A certain engineer "COMPLEX"

.NETでコード メトリックスを試してみる 第5回

前回は、保守容易性指数 について説明をしました。

今回も、個々のメトリックス値がどのように変化していくのか、をテーマに サイクロマティック複雑度 について説明します。

Explanation

おさらいですが、サイクロマティック複雑度はMSDNのコード メトリックス値というページでの説明によると、

サイクロマティック複雑度 - コードの構造上の複雑さを測定します。 これは、プログラムのフローにある、異なるコード パスの数を計算することで作成されます。 複雑な制御フローが含まれるプログラムでは、十分なコード カバレッジを実現するためにより多くのテストが必要となり、保守性が低下します。

と定義されています。

制御フローとは、ifswitchdowhileforeachfor の6つのステートメントを指します。
サイクロマティック複雑度は数値が低い程良好であることを示します。
また、サイクロマティック複雑度の増加は、保守容易性指数が低下と、テストケースの増加による作業工数の増加を招きますね。

では、サイクロマティック複雑度はどういう風に算出されているのでしょうか?
今回も簡単ではありますが、サイクロマティック複雑度が変化する様子をソースコードを用いて観察してみましょう。

Exercise

今回はステートメント毎に6つのメソッドに分離しました。

[csharp]
using System;

namespace CodeMetricsTest
{
class Program
{
static void Main(string[] args)
{
Case1();
Case2();
Case3();
Case4();
Case5();
Case6();
}

static void Case1()
{
var value = (new Random(0)).Next(0, 10);

if (value > 5)
{
Console.WriteLine("Value is upper than 5.");
}

if (value >= 0 && value <= 5)
{
Console.WriteLine("Value is 0 and over and 5 and less.");
}
}

static void Case2()
{
var value = (new Random(0)).Next(0, 10);

switch (value)
{
case 0:
Console.WriteLine("Value is {0}.", value);
break;
case 1:
Console.WriteLine("Value is {0}.", value);
break;
case 2:
Console.WriteLine("Value is {0}.", value);
break;
case 3:
Console.WriteLine("Value is {0}.", value);
break;
case 4:
Console.WriteLine("Value is {0}.", value);
break;
case 5:
Console.WriteLine("Value is {0}.", value);
break;
}
}

static void Case3()
{
var value = (new Random(0)).Next(0, 10);

do
{
value++;
Console.WriteLine("Value is {0}.", value);
} while (value > 10);

do
{
value++;
Console.WriteLine( "Value is {0}.", value );
} while ( value > 10 && value < 15 );
}

static void Case4()
{
var value = (new Random(0)).Next(0, 10);

while (value >= 0)
{
value--;
Console.WriteLine("Value is {0}.", value);
}

while (value > -5 && value < 0)
{
value--;
Console.WriteLine("Value is {0}.", value);
}
}

static void Case5()
{
var array = CreateRandomArray();

foreach (var value in array)
{
Console.WriteLine("Value is {0}.", value);
}
}

static void Case6()
{
var array = CreateRandomArray();

for (var index = 0; index < array.Length; index++)
{
Console.WriteLine("Value is {0}.", array[index]);
}

for (var index = 0; index < array.Length && index < 20; index++)
{
Console.WriteLine("Value is {0}.", array[index]);
}
}

static int[] CreateRandomArray()
{
var r = new Random(0);
var value = r.Next(10, 100);
var array = new int[value];

for (var index = 0; index < array.Length; index++)
{
array[index] = r.Next(10, 100);
}

return array;
}

}
}
[/csharp]

(2012.05.01 23:49 上記ソースが一部文字化けしていたのを修正しました。)

今回もサンプルとしては良いかもしれませんが...的なソースですが、これを Metrics.exe で計測してみましょう。
その結果が下記になります。

メトリックス項目Case1Case2Case3Case4Case5Case6
MaintainabilityIndex
(保守容易性指数)
716667677870
CyclomaticComplexity
(サイクロマティック複雑度)
474424
ClassCoupling
(クラス結合度)
222211
DepthOfInheritance
(継承の深さ)
------
LinesOfCode
(コード行)
587735

分岐ステートメントがあると サイクロマティック複雑度 が1増加するのではなく、そこに含まれる条件式の個数分だけ増加するようです。
たとえば、Case6メソッドの2つめのfor文には2つの条件式があるので、最初のfor文と併せて、1+1+2=4とサイクロマティック複雑度が計算されています。

では、条件式が含まれれば、サイクロマティック複雑度が増加するかというとそうではないようです。

[csharp]
using System;

namespace CodeMetricsTest
{
class Program
{
static void Main(string[] args)
{
Case1();
}

static void Case1()
{
if (true)
{
Console.WriteLine(&amp;quot;true!!&amp;quot;);
}

if (false)
{
Console.WriteLine(&amp;quot;False!!&amp;quot;);
}

bool value = false;
switch (value)
{
default:
Console.WriteLine(&amp;quot;False!!&amp;quot;);
break;
}

do
{
Console.WriteLine(&amp;quot;Break!!&amp;quot;);
} while (false);

while ( true )
{
Console.WriteLine(&amp;quot;Break!!&amp;quot;);
break;
}

for (; ; )
{
Console.WriteLine(&amp;quot;Break!!&amp;quot;);
break;
}
}

}
}
[/csharp]

上記のソースコード中のCase1メソッドは、foreach以外の全ての分岐ステートメントを含んでいますが Metrics.exe で計測してみても、サイクロマティック複雑度は1のままです。
つまり、「分岐が発生する」とサイクロマティック複雑度が1増加するため、分岐が発生し得ない、つまり必ず通る、通らないような条件式はカウントされません。
(foreach文は、要素列挙の終了条件がコレクション内部に隠蔽されているため、foraech文があるだけで+1されてしまうようです)

Conclusion

まとめると、サイクロマティック複雑度は、

  • 最も良好な値(分岐がない場合)は 1 であり、サイクロマティック複雑度の数値に上限はない。
  • 分岐ステートメントに含まれる条件式が1つ増える毎に +1 される。逆に分岐ステートメントがあっても、条件式が存在しない、または常に条件が同じであるような場合は +1 されない。
  • foreach文は存在するだけで常に +1 される。なぜならforeach文の内部に入るかどうかは、列挙されるコレクションの実装に依存するため、それ自体が条件式になってしまうから。

サイクロマティック複雑度を低下させるには、条件式を見直したり、条件式をメソッドとして抽出して、1つのメソッドに分岐を偏らせないようにする、等の方法が考えられます。

今回も長くなりましたが、サイクロマティック複雑度がどういうものかわかりました。
というわけで次回は 継承の深さ について、ソースコードを交えながら説明します。

コメントを残す

メールアドレスが公開されることはありません。

%d人のブロガーが「いいね」をつけました。