メソッドチェーンではテスト項目数は減らない

コードを短くすると単体テストが楽になる、の検証編 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2167

の続きで、実は適切なコードの書き方をしないとテスト項目は減りません。

# マーチン・ファウラー氏の言う「流れるようにコーディングをする」ことと
# コード自体の品質とは別なのです。
# 当然、コーディングがしやすい、というのも品質を上げる要因のひとつ
# であることは変わりありませんが。

これを検証してみます。
まずは、以下の3つのパターンを見てください。

A.通常パターン

Window win = new Window(800,600);
win.SetMessage("hello world.");
win.Show(0,0);

B.プロパティで設定するパターン

Window win = new Window();
win.Width = 800;
win.Height = 600 ;
win.Message = "hello world.";
win.X = 0;
win.Y = 0;
win.Show();

C.メソッドチェーンのパターン

Window win = new Window(800,600).Message("hello world.").Show(0,0);

非常に極端に書いていますが、典型的なコーディングの3つのパターンです。

A の通常パターンは、コンストラクタでサイズを指定して、その後でメッセージを設定しています。
表示をするときには、Show メソッドの引数で指定する、という関数呼び出し風なパターンですね。

この関数呼び出し風のパターンは、一見わかりやすそうですが、ぱっと見たときに引数が何を意味するのか分からないという欠点があります。例えば、次のように XY 座標をコンストラクタで設定するコードを書いたときは、

Window win = new Window(0,0,800,600);
win.SetMessage("hello world.");
win.Show();

なのか、

Window win = new Window(800,600,0,0);
win.SetMessage("hello world.");
win.Show();

なのかは、わかりませんよね。経験上、X,Y,Width,Height)の順と知っていても、それはマニュアルなど読まないと本当は分からないわけで、引数の意味はコード上には出て来ないのが普通です。

# よく MSDN で引数が混在しているコンストラクタやメソッド(Drawメソッド系とか)がありますが、この現象に陥っています。

さて、B のようにプロパティで指定する方法が、VB2.0(.NETにあらず)ありました。VBA でもこんな書き方をするし、tcl/tk もこんな書き方です。この書き方の欠点は、縦に長くなる(行数が多くなる)ということですね。大抵の場合、1 行にひとつの処理を書くことが多いので、A のコードよりも長くなるのが欠点です。
ただし、VB の場合は「:」を使ったり、C# の場合は「;」で複数行をまとめることができるので、

Window win = new Window();
win.Width = 800; win.Height = 600 ;
win.Message = "hello world.";
win.X = 0; win.Y = 0;
win.Show();

このように書いて、行数を短くすることができるのですが、A と B のパターンは決定的に違うところがあります。
A の場合は、Width と Height を同時に指定することができるのですが、B は、ひとつずつしか指定できません。なので、画面を表示したままのときに、Width, Height の順番に設定すると、横幅が変わった後に、縦幅が変わる、という現象が起こるのですね(実際、VB2.0 の頃には、そのような現象が発生していました)。なので、Width と Height の順番が重要で、実は変な感じなのです。

次は、最近流行(?)のメソッドチェーンの C パターンを見ていくと、あたかも 1 行で書けるために A パターンよりもすっきりしているように見えます。
コンストラクタで画面サイズを指定して、メッセージを追加して、画面の左上に表示するということがわかります。

ただし、これもメソッドの作り方が悪い、先の B のように順序が関係してきます。

Window win = new Window().Width(800).Height(600).Message("hello world.").X(0).Y(0).Show();

何をやっているのか、分かるような分からないような微妙なコードになります。C# の場合は、改行を入れることで少し読み易くできます。

Window win = new Window()
 .Width(800)
 .Height(600)
 .Message("hello world.")
 .X(0)
 .Y(0)
 .Show();

あれ?これって、B のパターンと同じですよね。
また VB.NET の場合は、With を使うと、B のプロパティのパターンを C のメソッドチェーンのように書けます。

Dim win = new Window();
With win
 .Width = 800
 .Height = 600
 .Message = "hello world."
 .X = 0
 .Y = 0
 .Show()
End With

ということはですね。実は、B のパターンで書いても、C のパターンで書いても、同じ数のテストをこなさないといけないのです。自由度が変わらないですからね。

じゃあ、A の通常パターンで書いた時には一番テストの数が少ないかというと、そうでもありません。

Window win = new Window(800,600);
win.SetMessage("hello world.");
win.Show(0,0);

1. コンストラクタの Window の引数は Width,Height なのか? Height,Width なのか?
2. Show メソッドの引数は、X,Y なのか? Y,X なのか?

という自由度が増えています。
実は、Window(0,0,800,600) としたときには、自由度が倍増します。実際コーディングをいくと実感できると思いますが、

func( int x, int y );
func( int x, string s );

ような 2 つの関数があったときには、int, int よりも、int, string のほうが間違いが減ります。
というのも、int, int の場合は、引数の順序を間違える可能性がありますが、int, string の場合はコンパイル時にはじかれてしまいますよね。これは、string, int のように逆のメソッドを作ることもできることを意味します(勿論、int,string と string,int の 2 つのメソッドが共存する場合は、どちらがどちらの意味なのかという別の自由度が発生しますが)。

というわけで、結論を言えば、A も B も C もテスト項目は同じぐらいになります。ゆえに、複雑度が同じという推測が立ちます。
推測を証明するために、先のようにメソッドと変数の数を勘定すると、

A パターン: 10
B パターン: 11
C パターン: 9

という数になります(引数が2つある場合は、それぞれの引数と交換を含めて自由度は3で勘定します)。
メソッドチェーンを使った場合は、若干小さくなりますが、思ったほど楽にならない、というのが検証できると思います。
まぁ、それでもインテリセンスが利いている最近の統合開発環境では、ピリオドを打った後にメソッドやプロパティ名が自動で出てくるのはいいですよね。メソッドの多重化が過ぎて 10 以上のメソッドから選ばないといけない、というアンチパターンよりいいと思います。

↓これとか、しんどくて…

Graphics.DrawImage メソッド (System.Drawing)
http://msdn.microsoft.com/ja-jp/library/9f3f6hy1.aspx

カテゴリー: 開発 パーマリンク