いよいよ? 長かった「.NETで○○○を試してみる」シリーズ第1弾「.NETでコード メトリックスを試してみる」も最終回。
感動の、涙なしには語れない (あまりの内容の薄さに哀れ過ぎて的な意味で)です。

前回は、クラス結合度 について説明をしました。

今回も、個々のメトリックス値がどのように変化していくのか、をテーマに コード行 について説明します。

さぁキビキビいきませう。

Explanation

最後のおさらいですが、コード行はMSDNのコード メトリックス値というページでの説明によると、

コード行 - コード内の行の概数を示します。
この数は IL コードに基づいているため、ソース コード ファイル内の正確な行数ではありません。
数が非常に大きい場合、型またはメソッドでの処理が多すぎるため、分割が必要であることを示すことがあります。
また、型やメソッドの保守が困難なことを示す場合もあります。

と定義されています。

今までで一番難しそうです(ネタの膨らませ方な意味で)

今回も簡単にソースコードを実査に観察して、コード行が変化する様子をソースコードを用いて観察してみましょう。

Exercise

今回のサンプルソースは下記です。
最後くらい真面目にやります。ええ。

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
namespace CodeMetricsTest
{
class Program
{
static void Main(string[] args)
{
}

private static void ForShort()
{
var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
for (int index = 0; index < array.Length; index++) System.Console.WriteLine(array[index]);
}

private static void ForLong()
{
var array = new int[]
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
};

for (int index = 0; index < array.Length; index++)
System.Console.WriteLine(array[index]);
}

private static void ForEachShort()
{
var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
foreach (var item in array) System.Console.WriteLine(item);
}

private static void ForEachLong()
{
var array = new int[]
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
};

foreach (var item in array)
System.Console.WriteLine(item);
}

private static void LinqShort()
{
var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
System.Array.ForEach(array, i => { System.Console.WriteLine(i); });
}

private static void LinqLong()
{
var array = new int[]
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
};

System.Array.ForEach(array, i =>
{
System.Console.WriteLine(i);
});
}
}
}

for,foreach,LINQ毎に、ShortとLongを用意しました。ShortとLongの際は、{,}で改行、空行を含むか否かです。
これを Metrics.exe で計測してみましょう。

下記はメトリックスの結果です。

メトリックス項目 ForShort ForLong ForEachShort ForEachLong LinqShort LinqLong
MaintainabilityIndex
(保守容易性指数)
81 77 82 78 77 77
CyclomaticComplexity
(サイクロマティック複雑度)
2 2 2 2 3 3
ClassCoupling
(クラス結合度)
2 2 2 2 4 4
DepthOfInheritance
(継承の深さ)
- - - - - -
LinesOfCode
(コード行)
2 3 2 3 3 3

結果を見ると、配列の宣言は 1 行 として計算されています。
for文もforeach文も改行すれば、適切に行数をカウントしているようです。
{,},空行はカウントされていません。

おもしろいのは、LINQ です。System.Array.ForEach です。改行しようがしまいが 2行 としてカウントされています。

ここまで来て「おかしいのでは?」と思った方。正しいです。
そう。一部誤差はあれど、あまりにもソースコードに忠実なカウントになっています。

思い出してください。コード行の定義を。この数は IL コードに基づいているため、ソース コード ファイル内の正確な行数ではありません。 というのを。

The truth

単純というか、説明不足というか。いや私のせいなんですが。

実はコード メトリックスの結果はある種の補正がかかっています。
何のことはありません。Metrics.exe は解析の時に、解析対象のアセンブリと同一ディレクトリに存在している pdb (プログラムデータベースファイル) を参照していたのです。

というわけで、その補正を外して真?の結果を見せましょう。
先の解析を行った CodeMetricsTest.exe と同一のディレクトリに存在している CodeMetricsTest.pdb を削除して、再度解析します。

1
2
3
4
5
6
warning : CA0068 : Debug information could not be found for target assembly 'Cod
eMetricsTest.exe'. For best analysis results, include the .pdb file with debug i
nformation for 'CodeMetricsTest.exe' in the same directory as the target assembl
y.
Writing report to CodeMetricsTest.xml...
Done.

警告が出ています。

「解析対象のアセンブリ CodeMetricsTest.exe のデバッグ情報がないよ。最適な解析結果を出すためには、対象のアセンブリと同じディレクトリに CodeMetricsTest.exe のデバッグ情報を含む .pdb ファイルを含めててくれよ。」(意訳)

もそんなの関係ねぇ。
その結果が下記です。

メトリックス項目 ForShort ForLong ForEachShort ForEachLong LinqShort LinqLong
MaintainabilityIndex
(保守容易性指数)
72 72 69 69 74 74
CyclomaticComplexity
(サイクロマティック複雑度)
2 2 2 2 3 3
ClassCoupling
(クラス結合度)
2 2 2 2 4 4
DepthOfInheritance
(継承の深さ)
- - - - - -
LinesOfCode
(コード行)
5 5 6 6 4 4

クラス結合度やサイクロマティック複雑度は変化していませんが、コード行が増加したため、保守容易性指数が低下しています。
正確な情報を必要とする場合は、PDBファイルは必須と言わざるを得ません。
また、デバッグ情報がないため、ShortとLongの結果が完全に一致しています。これこそ、「この数は IL コードに基づいているため、ソース コード ファイル内の正確な行数ではありません。」の証明に他なりません。
さらには、LinqShort、LinqLongのコード行が他より良くなっています。
が、これはまやかしです。

上記の画像の左側に、**b__2、b__0** というメソッドがコンパイラによって追加されています。
右側は b__2 の中身です。
単純に関数化してLinqShort、LinqLongのコード行が減ったように見えているだけです。

ただ、ILを見ても、コード行数の算出方法がわかりません。
どうやっているのでしょうか?これ。

Conclusion

まとめると、コード行 は、

  • {,}や空行はカウントの対象外。
  • 実行可能行のみカウントされるから、同じ内容でも、改行しないソースコードはコード行が低く見えてしまう。
  • 正確なコード行を計算するなら pdb (プログラムデータベースファイル) は必須。でないと、コード行が大幅に増加してしまう可能性がある。でも、pdb があっても正確な数値は出ない。

コード行を低下させるには、似たコードの関数化やロジックの見直しという地道な作業しか無いでしょう。
確かに改行を無くせばコード行は減りますが、それは本質では無いでしょう。
誤魔化した結果を用いたソフトウェア工学に何の意味があるのでしょう?そんなのは粉飾決算と同じです。

例の如く、最後まで、反省も無く、長くなってしまいましたが、コード行がどういうものかわかりました。
全部で8回に渡った 「.NETでコード メトリックスを試してみる」 でした。

難しい数式よりも実際のソースコードから結果の変化を見てもらうように努めましたがどうだったでしょうか? もう少し突っ込んだ内容のが良かったかなぁ、と個人的に思いましたが、そこまでスキルがあるわけでは無いので涙を飲むことになりましたが…

次回は、別の題材で 「.NETで○○○を試してみる」 を行います。

ふふふ。実はネタはもうできているのです。