.NET 正規表現の動作確認

C#、VB.NETで正規表現による検索を行った時の挙動を確認できます。
 
正規表現のパターンと検索対象を入力し実行ボタンをクリックすると、パターンにマッチした個所を黄色でハイライト表示します。
 
逐語的リテラル文字列「@"~"」を使用しない場合は、\マークをエスケープする必要があります。たとえば数字を検索する場合は「\d」を使用しますが、標準リテラル文字列の場合は「\\d」と指定する必要があります。
 
 
 
 
正規表現パターン:
new Regex(
 ",
    RegexOptions.
);
 
検索対象:

 
※正規表現パターンの改行コード、「\r\n」と「\n」は同一視します。
 

.NET String.IndexOfの動作確認

C#、VB.NETのString.IndexOfメソッドを実際に動かした時の挙動を確認できます。
 
ベースとなる文字列、値と検索の開始位置、文字数などを入力し実行ボタンをクリックすると、値が最初に出現する0から始まるインデックスを実行結果に表示します。
 
 ".IndexOf(" 
 ");
実行結果:
 
 
 ".IndexOf(" 
 ", 
 );
実行結果:
 
 
 ".IndexOf(" 
 ", 
 , 
 );
実行結果:
 
 
以下にString.IndexOfの挙動をまとめました。「試す」ボタンをクリックすると、書式を入力エリアにコピーしますので、実際の動きを確認することができます。

●書式一覧

取得内容 書式 結果 試す
"ABCBA"から"A"のインデックスを取得 "ABCBA".IndexOf("A"); 0
"ABCBA"から"A"のインデックスを取得。検索は"C"の位置から行う "ABCBA".IndexOf("A", 2); 4
"ABCBA"から"A"のインデックスを取得。検索は"C"の位置から3文字分行う "ABCBA".IndexOf("A", 2, 3); 4
"ABCBA"から"A"のインデックスを取得。検索は"C"の位置から2文字分行う "ABCBA".IndexOf("A", 2, 2); -1
 

.NET String.Substringの動作確認

C#、VB.NETのString.Substringメソッドを実際に動かした時の挙動を確認できます。
 
ベースとなる文字列、開始位置と文字数を入力し実行ボタンをクリックすると、取得した部分文字列を実行結果に表示します。
 
 ".Substring( 
 , 
 );
実行結果:
“”
 
 ".Substring( 
 );
実行結果:
“”
 
※実行結果の半角スペースは「¸」で表します。
 
以下にString.Substringの挙動をまとめました。「試す」ボタンをクリックすると、書式を入力エリアにコピーしますので、実際の動きを確認することができます。

●書式一覧

取得内容 書式 結果 試す
先頭から3文字取得 "ABあいう".Substring(0, 3); "ABあ"
2文字目から4文字取得 "ABあいう".Substring(1, 4); "Bあいう"
3文字目から最後まで取得 "ABあいう".Substring(2); "あいう"
 

条件判定による例外の置き換え

想定される状況なのに例外を使用している場合は「条件判定による例外の置き換え」を検討します。
 
たとえば「クーポンがある場合とない場合で割引価格を変更する」処理が以下のように定義されていたとします。
 
        // 販売価格を取得します
        private decimal GetSalePrice(bool hasCoupon, decimal listPrice)
        {
            try
            {
                return GetDiscountPrice(hasCoupon, listPrice);
            }
            catch
            {
                return GetDiscountPriceHasCoupon(listPrice);
            }
        }

        // 割引価格を取得します
        private decimal GetDiscountPrice(bool hasCoupon, decimal listPrice)
        {
            // クーポンがある場合
            if (hasCoupon == true)
            {
                throw new Exception();
            }

            return listPrice * 0.9M;
        }

        // 割引価格を取得します(クーポンあり)
        private decimal GetDiscountPriceHasCoupon(decimal listPrice)
        {
            return listPrice * 0.8M;
        }
 
見た目はまあまあ整っていますが、「クーポンがある場合」という正常系の処理に例外を使用している個所が問題です。
 
それでは「条件判定による例外の置き換え」のリファクタリングを適用してみましょう。
 
        // 販売価格を取得します
        private decimal GetSalePrice(bool hasCoupon, decimal listPrice)
        {
            // クーポンがある場合
            if (hasCoupon == true)
            {
                return GetDiscountPriceHasCoupon(listPrice);
            }
            else
            {
                return GetDiscountPrice(listPrice);
            }
        }

        // 割引価格を取得します(クーポンあり)
        private decimal GetDiscountPriceHasCoupon(decimal listPrice)
        {
            return listPrice * 0.8M;
        }

        // 割引価格を取得します
        private decimal GetDiscountPrice(decimal listPrice)
        {
            return listPrice * 0.9M;
        }
 
クーポンがある場合とない場合は「等しく発生する事象」ですので、「if – else」の条件判定で判断しそれぞれ割引価格取得用のメソッドを呼び分けています。
 

明示的なメソッド群による引数の置き換え

引数の特定の値に応じて、実行されるコードが分岐する場合は「明示的なメソッド群による引数の置き換え」を検討します。
 
たとえば割引価格のルールが「クーポンがある場合は定価の8割、ない場合は9割で販売する」だったとします。
 
上記を踏まえ「割引価格を取得する」メソッドを以下のように定義しました。
 
        // 割引価格を取得します
        private decimal GetDiscountPrice(bool hasCoupon, decimal listPrice)
        {
            // クーポンがある場合
            if (hasCoupon == true)
            {
                return listPrice * 0.8M;
            }

            return listPrice * 0.9M;
        }
 
これはこれで問題ないと思いますが、「明示的なメソッド群による引数の置き換え」を行うと引数の数を減らせます
 
        // 割引価格を取得します
        private decimal GetDiscountPrice(decimal listPrice)
        {
            return listPrice * 0.9M;
        }

        // 割引価格を取得します(クーポンあり)
        private decimal GetDiscountPriceHasCoupon(decimal listPrice)
        {
            return listPrice * 0.8M;
        }
 
このリファクタリングを行うと、条件分岐が呼び出し元に必要になります。「条件分岐をどこで行うのが適切か?」を考慮したうえで適用してください。
 

.NET DateTime.ToStringの動作確認

C#、VB.NETのDateTime.ToStringメソッドを実際に動かした時の挙動を確認できます。
 
DateTimeの値とToStringの書式を入力し実行ボタンをクリックすると、日時を指定の書式に成型し実行結果に表示します。
 
DateTime dt = new DateTime
 , 
 , 
 ,
 , 
 , 
 , 
 ); 
 
dt.ToString(" 
 "); 
実行結果:
“”
 
※実行結果の半角スペースは「¸」で表します。
 
以下によく使用する書式をまとめました。「試す」ボタンをクリックすると、書式を入力エリアにコピーしますので、実際の動きを確認することができます。

●書式一覧

成型内容 書式 結果 試す
日付 dt.ToString("d"); "2014/07/07"
dt.ToString("D"); "2014年7月7日"
dt.ToString("yyyy年MM月dd日"); "2014年07月07日"
※固定長
dt.ToString("yyyy/M/d"); "2014/7/7"
dt.ToString("yyyy/MM/dd(ddd)"); "2014/07/07(月)"
※固定長
dt.ToString("Y"); "2014年7月"
※年月
dt.ToString("M"); "7月7日"
※月日
時刻
※24時間表記
dt.ToString("t"); "9:06"
dt.ToString("T"); "9:06:02"
dt.ToString("HH:mm:ss"); "09:06:02"
※固定長
dt.ToString("H:m:s"); "9:6:2"
時刻
※12時間表記
dt.ToString("h:mm"); "9:06"
dt.ToString("h:mm:ss"); "9:06:02"
dt.ToString("hh:mm:ss"); "09:06:02"
※固定長
dt.ToString("h:m:s"); "9:6:2"
dt.ToString("tt h:mm:ss"); "午後¸9:06:02"
日付と時刻
※24時間表記
dt.ToString("g"); "2014/07/07¸9:06"
dt.ToString(); "2014/07/07¸9:06:02"
dt.ToString("G"); "2014/07/07¸9:06:02"
dt.ToString("yyyy/MM/dd HH:mm:ss"); "2014/07/07¸09:06:02"
※固定長
dt.ToString("f"); "2014年7月7日¸9:06"
dt.ToString("F"); "2014年7月7日¸9:06:02"
dt.ToString("yyyy年MM月dd日 HH:mm:ss"); "2014年07月07日¸09:06:02"
※固定長
日付と時刻
※12時間表記
dt.ToString("yyyy/MM/dd h:mm:ss"); "2014/07/07¸9:06:02"
dt.ToString("yyyy/MM/dd hh:mm:ss"); "2014/07/07¸09:06:02"
※固定長
dt.ToString("yyyy/MM/dd tt h:mm:ss"); "2014/07/07¸午後¸9:06:02"
dt.ToString("yyyy年M月d日 h:mm:ss"); "2014年7月7日¸9:06:02"
dt.ToString("yyyy年MM月dd日 hh:mm:ss"); "2014年07月07日¸09:06:02"
※固定長
dt.ToString("yyyy年M月d日 tt h:mm:ss"); "2014年7月7日¸午後¸9:06:02"
 

.NET String.Formatの動作確認

C#、VB.NETのString.Formatメソッドを実際に動かした時の挙動を確認できます。
 
String.Formatの書式と値を入力し実行ボタンをクリックすると、値を指定の書式に成型し実行結果に表示します。
 
書式指定:
String.Format(
 ",  
 );
 
プリミティブ型:
実行結果:
“”
 
※実行結果の半角スペースは「¸」で表します。
 
以下によく使用する幅指定、左詰め、右詰め、0埋め、カンマ編集、金額編集などの書式をまとめました。「試す」ボタンをクリックすると、書式を入力エリアにコピーしますので、実際の動きを確認することができます。

●書式一覧

成型内容 書式 結果 試す
幅指定(右詰め) 4文字の文字列(右詰め)に成型
String.Format("{0, 4}", 1);
"¸¸¸1"
幅指定(左詰め) 4文字の文字列(左詰め)に成型
String.Format("{0, -4}", 1);
"1¸¸¸"
0埋め 0埋め4文字の文字列に成型
String.Format("{0:D4}", 1);
"0001"
0埋め4文字の文字列に成型
String.Format("{0:0000}", 1);
"0001"
幅指定(右詰め)かつ0埋め 8文字の文字列(右詰め)に成型、そのうち4文字は0埋め
String.Format("{0, 8:D4}", 1);
"¸¸¸¸0001"
幅指定(左詰め)かつ0埋め 8文字の文字列(左詰め)に成型、そのうち4文字は0埋め
String.Format("{0, -8:D4}", 1);
"0001¸¸¸¸"
小数点編集 数値を小数点第二位に編集
String.Format("{0:F2}", 10.345);
"10.35"
幅指定(右詰め)かつ小数点編集 数値を小数点第二位に編集し、8文字の文字列(右詰め)に成型

String.Format("{0, 8:F2}", 10.345);
"¸¸¸10.35"
幅指定(左詰め)かつ小数点編集 数値を小数点第三位に編集し、8文字の文字列(左詰め)に成型

String.Format("{0, -8:F3}", 10.2345);
"10.235¸¸"
カンマ編集 整数をカンマ編集
String.Format("{0:N0}", 10000);
"10,000"
小数を含む数値をカンマ編集(小数点以下は2桁とする)
String.Format("{0:N2}", 12345.345);
"12,345.35"
幅指定(右詰め)かつカンマ編集 整数をカンマ編集し、8文字の文字列(右詰め)に成型
String.Format("{0, 8:N0}", 10000);
"¸¸10,000"
幅指定(左詰め)かつカンマ編集 小数を含む数値をカンマ編集し、10文字の文字列(左詰め)に成型
String.Format("{0, -10:N2}", 12345.345);
"12,345.35¸"
金額編集 整数を金額編集
String.Format("{0:C}", 10000);
"¥10,000"
小数を含む数値を金額編集(小数点以下は2桁とする)
String.Format("{0:C2}", 12345.345);
"¥12,345.35"
幅指定(右詰め)かつ金額編集 整数を金額編集し、8文字の文字列(右詰め)に成型
String.Format("{0, 8:C}", 10000);
"¸¥10,000"
幅指定(左詰め)かつ金額編集 小数を含む数値を金額編集し、11文字の文字列(左詰め)に成型
String.Format("{0, -11:C2}", 12345.345);
"¥12,345.35¸"
パーセント編集 数値に100をかけ、「%」記号を付ける
String.Format("{0:P2}", 0.12345);
"12.35%"
幅指定(右詰め)かつパーセント編集 数値をパーセント編集し、8文字の文字列(右詰め)に成型
String.Format("{0, 8:P2}", 0.12345);
"¸¸12.35%"
幅指定(左詰め)かつパーセント編集 数値をパーセント編集し、8文字の文字列(左詰め)に成型
String.Format("{0, -8:P2}", 0.12345);
"12.35%¸¸"
16進数変換 数値を16進数に変換
String.Format("{0:X}", 10);
"A"
数値を4文字の16進数に変換(桁が足りない場合は0埋め)
String.Format("{0:X4}", 10);
"000A"
 

例外によるエラーコードの置き換え

メソッドがエラーコードを返すため、正常系と異常系が分かりづらい場合は「例外によるエラーコードの置き換え」を検討します。
 
たとえば「面積を取得する」メソッドが以下のように定義されていたとします。
 
        // 面積を取得します
        private int GetArea(int width, int height)
        {
            // 幅または高さが0以下の場合
            if (width <= 0 || height <= 0)
            {
                return 0;
            }

            return width * height;
        }
 
どうやら幅または高さに0以下を指定した場合は、エラーコードである「0」を返しているようですが、これが正常値なのか異常値なのか判断に迷うところですよね?
 
「幅または高さに0以下が指定された場合は異常」を明確にするため、「例外によるエラーコードの置き換え」を行います。
 
        // 面積を取得します
        private int GetArea(int width, int height)
        {
            // 幅または高さが0以下の場合
            if (width <= 0 || height <= 0)
            {
                throw new ArgumentException();
            }

            return width * height;
        }
 
呼び出し元で例外を処理する必要はありますが、正常系と異常系を切り分けることができました。
 

パラメータへの代入の除去

引数に対して代入している個所があったら、代わりに一時変数を使用します。
 
たとえば「四角を描画する」メソッドが以下のように定義されていたとします。
 
        // 四角を描画します
        private void DrawRectangle(int x, int y, int width, int height)
        {
            // x座標が0より小さい場合
            if (x < 0)
            {
                x = 0;
            }

            // y座標が0より小さい場合
            if (y < 0)
            {
                y = 0;
            }

            ・・・
        }
 
どうやらx座標とy座標には0以上の値を指定しなければならないようで、0より小さい値が指定された場合に0へ補正する処理が入っています。
 
しかし0を「パラメータへ直接代入」している個所が問題です。
 
上級プログラマーがパラメータの「値渡し」と「参照渡し」を適切に切り分け、可読性を考慮して行っている場合もありますが、たいていは「面倒くさい」という理由で行われたバグの源です。
 
後の修正でこのようなコードに出会った場合、プログラマーは「値渡し」か「参照渡し」かを確認し、自分が行う修正が呼び出し元まで波及するかまで調べなければなりません。
 
これではプログラマーがどんなに優秀でも保守性が向上しませんので、「パラメータへの代入の除去」のリファクタリングを行いましょう。
 
この例だと三項演算子を使うとキレイにまとめられそうです。
 
        // 四角を描画します
        private void DrawRectangle(int x, int y, int width, int height)
        {
            // x座標を補正
            int correctX = (x < 0) ? 0 : x;

            // y座標を補正
            int correctY = (y < 0) ? 0 : y;

            ・・・
        }
 
ここで重要なことは三項演算子を使ったことではなく、「値を補正するため、一時変数を使用した」ことですので、お間違いのないようよろしくお願いします。
 

ガード節とド・モルガンの法則

ガード節による入れ子条件記述の置き換え」のリファクタリングは、異常系のチェックを先に行い、returnや例外をスローすることで正常系の処理を単純化できますが、元のソースのつくりによっては非常にやりづらいリファクタリングです。
 
たとえば「年齢20歳以上、30歳未満の場合のみ登録する」といった処理があったとします。
 
        // 登録します
        private void Register(int age)
        {
            if (20 <= age && age < 30)
            {
                // 正常系の処理
                ・・・
            }
        }
 
このくらい単純な処理であれば正常系のif文だけでもいいかもしれませんが、たいていは複合条件になりますので、ここはキチンとガード節を作りましょう。
 
と簡単に申し上げましたが、正常系のif文から異常系のガード節を作成するのは難しい場合があります。
 
ここで有効なのが「ド・モルガンの法則」です。
 
ド・モルガンの法則

ド・モルガンの法則

 
手順としては「正常系を否定すれば異常系のガード節になる」ので、ソースを以下のように改修します。
 
        // 登録します
        private void Register(int age)
        {
            if (!(20 <= age && age < 30))
            {
                return;
            }

            // 正常系の処理
            ・・・
        }
 
これでは条件が複雑になっただけですので、ここに「ド・モルガンの法則」を適用します。
 
先ほどの図をプログラミング言語で表現すると、
 
!(A || B) == !A && !B
!(A && B) == !A || !B
 
となりますので、上記ルールに従って改修すると、
 
        // 登録します
        private void Register(int age)
        {
            if (!(20 <= age) || !(age < 30))
            {
                return;
            }

            // 正常系の処理
        }
 
ここまで分解できれば、後は条件一つずつ人間が分かりやすい形で表記すればよいので、
 
        // 登録します
        private void Register(int age)
        {
            if (age < 20 || 30 <= age)
            {
                return;
            }

            // 正常系の処理
        }
 
となります。ド・モルガンの法則を知っていれば、業務ロジックが分からなくてもガード節は作れます。
 
最初は難しいかも知れませんが、反復しているうちに使いこなせるようになりますので、がんばって覚えてください。
g h T
 4,287 Total Views