LINQ、ラムダ式を使ってみた
LINQ、ラムダ式を使ってみて、自分の中でコーディングスタイルに変化が表れてきました。
まだまだ全然モノにできていませんが、現時点で感じている変化について、メモしておこうと思います。
LINQを使うとfor文(foreach文)が減る
LINQを使うと配列、コレクションといった集合や集合に含まれる各要素に対する操作を簡潔に記述できます。
集合の各要素に対する操作とは、いわゆる繰り返し操作のことです。
これまでfor文(foreach文)で記述していた内容が、LINQに取って代わられる場面が増えました。
例えば、
foreach(var p in ...)
{
if(condition(p))
{
hoge(p);
}
}
は、LINQを使うと、
....Where(p =>
{
return condition(p);
}).All(p =>
{
hoge(p);
return true;
});
などと書くことができます。
処理が簡単なうちはfor文(foreach文)で問題ないのですが、処理が複雑になればなるほどLINQの方が簡潔に記述できる感じがします。
オブジェクト指向が流行り始めた頃、ポリモルフィズムを利用してswitch文の記述を減らすという話がよくありましたが、なんとなく近いものを感じます。
LINQを使うとコードが宣言的になる
LINQはSQL文(SELECT文)によく似ており、LINQを使うとコードが宣言的になります。
宣言的なコードは、実現方法や手順ではなく、実現内容そのものを記述します。
したがって、一般に読みやすい、理解しやすいコードになると言われています。
一方、デバッグやテストのことを考えた場合、必ずしもうれしいことばかりではないかもしれません。
ラムダ式を使うと関数の共通化が進む
『同じコードを2か所に書かない』というのはプログラミングの定石ですが、実際のコードでは、同じようなコードを書いてしまっていることがあります。
全くのコピペではないものの一定のパターンに則って同じような処理が何度も記述されるケースは多いと思います。
もし、異なるインスタンスに対して同一の処理をしている箇所がある場合は、その部分を共通関数として括り出し、インスタンスを引数で渡すようにすることが可能です。
例えば、
if(x.a())
x.b();
else
x.c();
と
if(y.a())
y.b();
else
y.c();
というコードブロックがあったら、
func(x);
func(y);
func(obj p)
{
if(p.a())
p.b();
else
p.c();
}
とすることができます。
これは従来から行われている共通関数化です。
ラムダ式を使うと、これに加え、一部が異なっているが概ね同様の処理をしている箇所がある場合に、異なる場所をラムダ式にして外部から渡すタイプの共通関数を括り出すことが可能になります。
例えば、
if(xxx())
b();
else
c();
と
if(yyy())
b();
else
c();
というコードブロックがあったら、
func(p => xxx());
func(p => yyy());
func(Func<bool> fn)
{
if(fn())
b();
else
c();
}
とすることができます。
これにより、従来からある部品としての共通関数ではなく、骨組みとしての共通関数を記述することができるようになったと感じています。
ラムダ式を使うとクラス数が減る
今日、実装上の問題を解決する方法として多くのデザインパターンが知られています。
これらのデザインパターンはクラスベースのオブジェクト指向を前提としており、デザインパターンを適用した結果、定義されるクラスの数が増えることになります。
一方、実装段階でクラスの数が増えすぎると、概念設計と実装との間でクラスのミスマッチが大きくなり、クラスをトレースすることが困難になってきます。
デザインパターンが解決する問題のいくつかは、ラムダ式で置き換えることが可能です。
ラムダ式を使うことで概念クラスから実装クラスへのトレーサビリティを維持しやすくなると感じています。