コードを読んで品質を測定する
さて、次は真正面からコードを読んで品質を測定する方法を紹介します。コード品質を測定する指標は多数あるのですが、この章ではそのうちごく基本的なものを3つ、その具体的な測定手法とともに紹介します。
コード行数(LOC:Line Of Code)
概要
コード品質を測定する際に、最もよく使われる指標はコード行数、いわゆるLines-of-code(LOC)でしょう。古くからある指標で、計測も簡単にできます。Linux 環境であれば、wc -l コマンド一発ですし、エディタで常時表示させることもできます。
ただしLOCは取り扱いが難しい指標でもあります。特に、LOCをコード品質ではなく、作業量の見積もりや、開発チームのパフォーマンスの計測に使う場合、解釈を誤ると批判が生まれます。例えば、2つの異なるチームが「オンライン決済システム」を開発した時に、同じ期間で10,000行のコードで開発したのと、1,000行のコードで開発したのでは、どちらのチームがパフォーマンスがよいと言えるでしょうか。ほぼ同じ機能であるならば、その後の保守メンテナンスのことも考えると、より少ない行数で実現できた方がいいですよね。
指標の使い方
取扱いが難しい指標ではありますが、それでもLOCは有用です。例えば、LOCはテストや保守の基準値として使われることがあります。10万行のソフトウェアは、1万行のソフトウェアよりも保守やテストのコストがかかるのは同意できるところでしょう。バグの数をLOC で割ったものを「バグ密度」と定義し、ソフトウェア品質の指標として使っているチームもあります。
LOCの計測方法にはいくつかの定義があり、チームや組織で採用する場合は同じ定義を一貫して使うことが重要です。単純にテキストファイルとしての行数(Physical LOC)を使うこともありますが、空行とコメント行を除いた行数(Non-comment LOC:NLOC)や、NLOCからさらに、「2つの命令が書かれた行は2行と数える」「括弧だけの行を除く」などの換算をした行数(Logical LOC:LLOC)を用いることも多いでしょう。コメントの行数をComment lines of program text(CLOC)として計算し、ソースコード中のコメント密度をCLOC/LOCとして定義し、一定以上を保つようなルールを設けているチームもあります。
循環的複雑度(Cyclomatic Complexity)
概要
循環的複雑度は、大雑把にいえばコード内のif/else、for、switch、whileなどの分岐やループの数を数え上げたものです。細かい話が苦手な方は以下を飛ばして、「指標の使い方」に進んで下さい。実用上はそれでなんの問題もありません。
循環的複雑度は、プログラムの構造を「制御フローグラフ(Control flow graph)」で表した際のグラフ内部の循環の数に着目してソフトウェアプログラムの複雑性を測定する指標です。1976年にMcCabe によって提案されました。ちなみに制御フローグラフとは、プログラムの実行中にプログラムを通過する可能性のあるすべての経路を有向グラフで表現したもので、Frances E. Allen によって提唱されたものです。
図2に制御フローグラフの例を示します。途中に分岐や反復を含まない一つながりのコードを基本ブロックといいますが、制御フローグラフのノードはこの基本ブロックです。
図のaからfがノードです。ノードaがプログラムの開始位置でノードfが終了位置です。
条件分岐などによる基本ブロック間のつながりはエッジで表されます。図では実線の矢印です。
循環的複雑度の定義は以下のとおりです
V(G) = E – N + 2P
式中の変数は以下のように定義します。
V:循環的複雑度
G:制御フローグラフ
E:制御フローグラフのエッジ(辺)の数
N:制御フローグラフのノード(頂点)数
P:連結されているコンポーネントの数
また、Pの「コンポーネントの数」は、ひとまとまりのグラフとしてまとめられる数です。単一のプログラム(またはサブルーチンやメソッド)を対象にしている場合は、グラフのまとまりは1つなので、P=1 となります。よって、より簡略化された循環的複雑度V'は
V'(G) = E – N + 2
となります。例として挙げた図2の制御フローグラフで循環的複雑度を計算すると
V'(G) = エッジの数 9 – ノードの数 6 + 2 = 5
となります。McCabeの循環的複雑度は、プログラムの制御グラフを「強連結(Strongly Connected)グラフ」に変換したときの、その強連結グラフ中の(線形独立な)ループの数です。強連結グラフとは、すべての頂点が他のすべての頂点から到達可能であるグラフです。図2を例に説明すると、プログラムの流れとしては1から9のエッジ(実線)のみですが、これに仮想的なエッジ10(点線)を追加します。追加後のグラフが強連結グラフです。このグラフの中にループは5つあるので、循環的複雑度は5になります。5つのループの1つはノードaからb、e、fとたどりaに戻る経路です。残りの4つはbeb、abea、acfa、adcfa という経路のループです。
指標の使い方
循環的複雑度を計算するために、有償・無償の様々なツールが提供されています。代表的な例を挙げると以下のようなものがあります。
また、Microsoft Visual Studioや、IntelliJ IDEAなどの統合開発環境でも利用できます。
計算で得られた循環的複雑度の解釈については諸説ありますが、MathWorks社のものを引用すると以下のとおりです。
循環的複雑度 | 複雑さの状態 | バグ混入確率 |
---|---|---|
10以下 | 非常に良い構造 | 25% |
30以上 | 構造的なリスクあり | 40% |
50以上 | テスト不可能 | 70% |
75以上 | いかなる変更も誤修正を生む | 98% |
Gradyによる1994年の論文によれば、Hewlett-Packard社における850,000行のFortrunのコードを分析した結果、モジュールの循環的複雑度とそのコードが編集された回数には密接な関係があったとしています。3回以上の変更が必要なモジュールについての変更コストとスケジュールの影響を分析した結果、この論文ではモジュールに許容される最大の循環的複雑度は15と結論づけています。他に例を上げれば、例えばMicrosoft Visual Studioでは、循環的複雑度が25を超えるとワーニングが出て、リファクタリングを促します。