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

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

Explanation

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

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

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

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

Exercise

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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;
}

}
}

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

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

メトリックス項目 Case1 Case2 Case3 Case4 Case5 Case6
MaintainabilityIndex
(保守容易性指数)
71 66 67 67 78 70
CyclomaticComplexity
(サイクロマティック複雑度)
4 7 4 4 2 4
ClassCoupling
(クラス結合度)
2 2 2 2 1 1
DepthOfInheritance
(継承の深さ)
- - - - - -
LinesOfCode
(コード行)
5 8 7 7 3 5

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using System;

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

static void Case1()
{
if (true)
{
Console.WriteLine("true!!");
}

if (false)
{
Console.WriteLine("False!!");
}

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

do
{
Console.WriteLine("Break!!");
} while (false);

while ( true )
{
Console.WriteLine("Break!!");
break;
}

for (; ; )
{
Console.WriteLine("Break!!");
break;
}
}

}
}

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

Conclusion

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

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

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

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