煩雑になった定義部分を省略する

前回の記事の続きです。Tupleを使うのがいいのか悩んでいた面もあったので、コードが冗長になっていました。
以下、記述量を減らしたパターンです。

            Func<Func<bool>, string, Tuple<Func<bool>, string>> te = (f, s) => Tuple.Create(f, s);
            var 確認事項2 = new List<Tuple<Func<bool>, string>>{
                te((()=>a==b),"一致ダメ"),
                te((()=>a>b),"aが大きくてはダメ"),
                te((()=>b > a * 10),"bが大きすぎてもダメ"),
            };

一見、最初に書くのが面倒くさそうなイメージがありますが、各データのイメージがあるので書けます。後からメンテナンスするときは、引数部分だけ検討すればいいのでお手軽です。

エラー処理で行いがちな、条件判定ブロックを整理する

エラー判定を行うときに、簡単に書いてしまうと下記の記述になることがあります。

            int a = 5;
            int b = 10;

            if (a == b) {
                Console.WriteLine("一致ダメ");
                return;
            }

            if (a > b) {
                Console.WriteLine("aが大きくてはダメ");
                return;
            }

            if (b > a * 10) {
                Console.WriteLine("bが大きすぎてもダメ");
                return;
            }

return;が入っていることから、これはこれ以上まとめようがないと思っていました。
ただこれをじっと見ていたら、次のまとめ方に気づきました。

            var 確認事項 = new List<Tuple<bool, string>>{
                Tuple.Create((a==b),"一致ダメ"),
                Tuple.Create((a>b),"aが大きくてはダメ"),
                Tuple.Create((b > a * 10),"bが大きすぎてもダメ"),
            };

            foreach (var s in 確認事項) {
                if (s.Item1) {
                    Console.WriteLine(s.Item2);
                    return;
                }
            }

ここで、次のネタが発生します。
aとbの大小関係を評価するタイミングはどこなのか? というネタです。
上記の例では、変数への代入時ですので、この後a,bの値が変更になるコードの場合には、意図した動作にはなりません。

そういったケースでは次の書き換えを行います。

            var 確認事項2 = new List<Tuple<Func<bool>, string>>{
                Tuple.Create((Func<bool>)(()=>a==b),"一致ダメ"),
                Tuple.Create((Func<bool>)(()=>a>b),"aが大きくてはダメ"),
                Tuple.Create((Func<bool>)(()=>b > a * 10),"bが大きすぎてもダメ"),
            };

            foreach (var s in 確認事項2) {
                if (s.Item1()) {
                    Console.WriteLine(s.Item2);
                    return;
                }
            }

ラムダ式の評価タイミングが意図したタイミングに合うと思います。
ちょっと定義部分の記述が多すぎるので、簡潔にしたいところです。
同様のパターンで条件判定の後に異なる動作を入れる場合は、stringの部分をラムダ式にしてしまえば実現できます。

大きくなりすぎたクラスを整理するには

リファクタリングを行っていると、大幅な書き換えを行ってしまったことにより、旧コードのメソッド群が作業の邪魔になることがあります。
旧コードは消してしまえればそれに越したことは無いのですが、なんらかの心理的抵抗があるのも事実です。
見たくない範囲を#region で隠してしまったりもするのですが、何かの拍子に展開されてしまって、いちいち癇に障ります。
そんなときの、いかにも実践的な泥臭い解決方法。

partial class を1つ新設し、本来なら消してしまうようなコードをまとめてそちらに移動する。

乱暴に見えますが、本気でメンテナンスしたいファイルの(見た目の)行数が減るので作業効率があがります。
上記の変更は、コピーさえ失敗しなければ、プログラムの動作は変わりません。

ターゲットをn個の数値と比較するコード

if文で、if (tag == col) といったコードはごく普通に書きます。
これが仕様変更により、比較対象が1つではなく、n個になることもよくあります。
コードを書く側が実現したいことは、

  • できるだけコードを変更したくない
  • プログラムの流れ的に、tagを評価している式と分かる実装にしたい
  • 比較対象は綺麗に列挙したい

といった内容があります。

そこで、こんな形で解決してみました。

int [] cols = {1,2,3};
if (cols.Any(x => x == tag)) ...

等号の左右の位置関係が変わるのが生理的に気持ち悪いかどうかですね。
(もちろん、ひっくり返して書くこともできますが、ラムダ式の記述パターン的にこちらにしてみました。)
変更箇所は少なく抑えられていると思います。

Visual Studio 11 developer’s preview をインストールするとVS2010のAzureプロジェクトでエラーが発生する

仮想マシンが一般的となった今日では、開発環境の大幅変更を使っている環境で行うと
いった愚は犯さないと思いますが、いちおうノウハウとして書いておきます。

Visual Studio 2010 をインストールし、Azureプロジェクトが作成できるように
環境構築を行なっていたところに、Visual Studio 11 developer’s preview を
インストールすると、
The imported project c:\Program Files\MSBuild\Microsoft\VisualStudio\v11.0\Windows Azure Tools\1.4\Mirosoft.WindowsAzure/targets” was not found.
というエラーが出て、新規プロジェクト作成がエラー終了します。

対処療法ですが、上記のvisualStudio\v10.0 以下にあるWindows Azure Toolsフォルダを
v11.0の下にもコピーするとエラーは出なくなります。(モジュールを正常にビルドできる保証はありません。)

毎回記述ミスをするDateTimeのAddHour()

気をつけているのに、毎回のように記述ミスをする内容があります

DateTime d = DateTime.Now;
d.AddHour(1);

コンパイルエラーにならないので、厄介です。
i++; があるからなのか、d = d.AddHour(1); とキチンと書けなくて泣けます。

変数の最大値/最小値を定義する2

biacさんからツッコミがあったので、その2に追記します。

1行で0以上479以下の範囲に変数のとる値を抑える構文を1行で書くならば、

t = Math.Max(minvalue, Math.Min(t, maxvalue)

で可能です。2回Math.を書くのが何か嫌だったのと、一目ではやりたいことが
わかりにくいのかなぁという危惧があったので躊躇しました。
更に、拡張メソッドを使って下のようにする方法もあります。

namespace GpExt {
    public static class GpFilter {
        public static int CutOff(this int target ,int minvalue, int maxvalue) {
            return Math.Max(minvalue, Math.Min(target, maxvalue));
        }
    }
}

と定義したうえで、対象とするtに対して
t = t.CutOff(0, 479);

ただし、拡張メソッドにしてしまうと定義位置が遠くなってしまって見づらいという危惧があります。
そこで、こんな解決方法を考えて見ました。

            const int MinVal = 0;
            const int MaxVal = 479;
            Func<int, int> coff = (target) => Math.Max(MinVal, Math.Min(target, MaxVal));

            t = coff(t);

実際に使用する場所の近い位置で定義できるので、定義内容は見やすくなります。
ただし、coff(t)と、t.CutOff() を比べた場合、処理の流れが左から右に流れるイメージで
扱いたいときには、t.CutOff()の方が綺麗に見えます。

ここまでは、普通の展開であると思います。
ここからは、最近少し悩んでいる内容です。
より実践的なコードを考えたます。X座標とY座標でCutOffしたいと考えます。
最大値・最小値の定数を4つ持つことになるわけです。Func にしてしまえば、
対応できますが、その方向で使用すること、引数に範囲を毎回いれていくことは作業ミスを
誘発しやすい内容になります。
そこで、思い切った書き方に変えてみます。

            Func<int, int> Xcoff = (target) => Math.Max(0, Math.Min(target, 479));
            Func<int, int> Ycoff = (target) => Math.Max(0, Math.Min(target, 799));

定数値をFuncに埋め込んでしまいました。
メソッドの定義部でFuncを定義するという概念で考えれば、強引ですが辻褄は合いそうです。
これが4パターン程度必要になるならば、Func<>を2段にしてもいいのかもしれません。
結局のところ、実践で使ったときのスコープと頻度を考えて、よりすっきりできる方向の記述を採用することになると思います。

変数の最大値/最小値を定義する

数値を扱う変数には、たいてい仕様から最大値/最小値がついてまわります。
最大値として479をとるintの変数があった場合は、従来ならばif文を
使用して最大値の設定を行なっていました。

先日サンプルコードを見ていたら、そういえばそうかと思う記述があったので
自分のためにメモしておきます。

int x = 530;
x = Math.Min(x,479);

最小値に関しては、Math.Max()で同様に処理できます。

よくあるケースとして 0以上480未満 という最大値、最小値を押さえるパターンが
あるのですが、これは2行かかってしまいます。もっとクールな方法があれば教えてください。

3種類の計算式の結果から最大値を取得する

システムを作るにあたって、データ同士の演算は必ずといっていいほど発生します。
本当にやりたいことは、
「計算で得られたn個の結果の中から最大値を返す」
と、日本語だと1行で表されることでも、今までのプログラムでは
ソートを使わないといけないと感じたり、ソートまで大げさなこと
しなくても高々n個だから、ループでまわして大小比較するロジックを
でっち上げたりと、意外なほど面倒でした。

日本語書くのも面倒なので、これ以上の言及は避けます。
C#で書くとすでに、モジュール化することが嫌になるほど簡単です。

                decimal[] trs = { a.HighPrice - a.LowPrice, a.HighPrice - pd.ClosePrice, pd.ClosePrice - a.LowPrice };
                decimal tra = trs.Max();

一箇所で使うだけならば、traのテンポラリ変数で受ける必要もないです。

記述をラムダ式に置き換えていく方法

私のショートコードの書き方は以下のステップになっています。

  • まずは作成したい内容をベタ書きする
  • 同じ内容をまとめる
  • まとめた部分を別の表現方法で表現する

地道ですが、現状作っているコードを少しずつ手直しすることで、
短くしていくことができます。それも機械的に。
実戦でも使用可能なのでなかなか良いです。
その訓練を続けるうちに、最初から短いコードが書けるようになります。

これをやっていたら、以前から懸念していた引数にラムダ式を書くやり方も
同じやり方でできることに気づきましたので紹介します。

        private void foo() {
            Color a = button1.BackColor;
            if (a == Color.Red) Console.WriteLine("aaa");
            if (a == Color.Red) boo();
        }

簡単ですがこのようなコードがあったとします。
同じ内容をまとめる作業を行います。

        private void foo() {
            Color a = button1.BackColor;
            Func<Color, bool> f = x => (x == Color.Red);

            if (f(a)) Console.WriteLine("aaa");
            if (f(a)) boo();
        }

最初は関数内に定義したローカル変数ですが、だんだん関数外で設定したい要求がでてきます。
その場合、Color a を引数にする書き換えはとても簡単です。
このときに、Func f も、変数と同じと捉えて、引数に置き換えてしまうことができます。
ついでにif文が冗長なのでまとめておきます。

        private void goo(){
            Color a = button1.BackColor;
            Func<Color, bool> fa = x => (x == Color.Red);
            foo(a,fa);
        }

        private void foo(Color a, Func<Color,bool> f) {
            if (f(a)){
                Console.WriteLine("aaa");
                boo();
            }
        }

同じコードをまとめる、位置を変えるという機械的な作業をするだけで、
引数にラムダ式をもった関数が出来上がりました。
(ただのサンプルなので、上記コード自体に大した意味・優位性はありません。)
このやり方で書き換えを行うことで有効となる局面があります。