give IT a try

プログラミング、リモートワーク、田舎暮らし、音楽、etc.

数値で測るコード品質

プロフィールのところにも書いてあるのですが、おいらの目標は「美しく無駄のないシステムアーキテクチャを設計、構築すること」です。
なぜならシステムの保守性や拡張性って、アーキテクチャやコードの品質によって雲泥の差が出ることを、幾度となく痛感してきているからです。


しかし、どんなアーキテクチャソースコードがキレイなのか、拡張性や保守性が高いのか、っていうのはなかなか客観的に判断しにくいのもたしか。


「俺のコードはあんたのより分かりやすい」
「そうですか??」
「絶対そうやろ!?なんでわからんのや???」


みたいな議論が始まったらたぶん平行線になっちゃいますよね。
そこでツールを使って、コードの品質を定量的に測ってみることにしました。

今回使ったツール

今回使ったツールはこちらです。


SourceMonitor V3.2
Duplo | Free Development software downloads at SourceForge.net


SourceMonitorはコードの複雑さやネストの深さ、コードの行数などを分析してくれるフリーソフトです。
Duploはコードの重複を発見してくれるフリーソフトです。
ただし、Duploは自分でビルドする必要があるのでC++コンパイラが必要です。
おいらはVisualStudioを使ってビルドしました。


会社ではC#を使うことが多いので、C#で使えるフリーソフトを探したのですが、有料のソフトが多いのでフリーソフトを見つけるのにはちょっと苦労しました。

サイクロマチック複雑度について

SourceMonitorではサイクロマチック複雑度を計算します。
簡単に言うと一つのメソッドや関数の中に条件分岐やループが存在すると、その度に複雑度がカウントアップされるものです。
複雑度が10以下であれば「良い構造」とされるようです。
サイクロマチック複雑度についてはあきぴーさんのページを参考にしてみてください。


複雑度と単体テストケース数の相関関係: プログラマの思索

計測対象のプロジェクト

今回は4つのプロジェクトを比較してみました。


一つ目はおいらがアーキテクト兼プログラマとして現在取り組んでいる開発中の社内システムです。
まだリリースできていませんが、開発はほぼ終盤なので今後大きな変更が発生することはあまりないはずです。


二つ目は5年ぐらい前に構築された某社内システムです。
おいらの入社前に構築されたシステムなので、おいらは設計にも実装にも全く関わっていません。
かつてこのシステムのある画面に「表示項目を一つ増やす」という改造案件を担当したのですが、簡単だろうと思って蓋を開けてみたら、どこから手をつけていいのかさっぱり分からん惨状が広がっていました。
ちなみにこの改造案件については結局、システムの改造は諦め、別の対応策を提供することでクローズしました。


三つ目はNUnitです。
NUnitソースコードはキレイで参考になる」という話をどこかで聞いたので、比較用に使ってみました。


四つ目はlog4netです。
NUnit以外のオープンソースプロジェクトを比較対象にしたかっただけなので、log4netを選んだ深い意味は特にありません。

比較対象のソースコード

ユニットテスト用のソースコードは対象外としています。
自動生成されたコードも対象外です。
また、某社内システムでは「どうみても使っていなさそうなコード」がいくつかリポジトリに残っていたので、独断で除外しました。

測定結果

というわけで以下がSourceMonitorを使って計測した各種メトリクスです。
(数値が左寄せになっていてちょっと見にくいですね。すみません)


各値は以下の意味になります。

File
ファイル数
Lines
コード行数(空白行やコメント行も含む)
Max Complexity
Projectで最も複雑なメソッドの複雑度
Max Depth
Projectで最もネストが深いメソッドのネストの深さ(9+は9以上)
Avg Depth
Project全体のネストの深さの平均
Avg Complexity
Project全体の複雑度の平均
Project Files Lines Max Complexity Max Depth Avg Depth Avg Complexity
開発中システム 98 15,323 13 7 1.82 1.5
某社内システム 26 13,755 47 8 2.56 3.26
NUnit 348 59,322 35 9+ 2.05 1.89
log4net 203 53,103 45 9+ 2.12 2.03


NUnitlog4netは規模が大きいので単純な比較は出来ないかもしれませんが、おいらが設計したシステム(一番上)が複雑度やネストの深さの面で一番低いになっていますね〜(自画自賛)。
反対に、おいらも困らされた某社内システム(2段目)は一番高い値になっています。


また、NUnitlog4netを比較すると、NUnitの方がやや低い値になっている事が分かります。


次に各プロジェクトでMax Complexity(最大複雑度)が高い上位10ファイルを順に並べてみます。

開発中システムの場合
File Name Lines Max Complexity Max Depth
UI系.cs 470 13 4
UI系.cs 779 11 4
ビジネスロジック系.cs 433 8 6
UI系.cs 406 8 6
UI系.cs 621 8 4
UI系.cs 342 8 4
UI系.cs 316 8 3
UI系.cs 329 7 6
UI系.cs 146 7 4
UI系.cs 154 7 3


複雑度(Max Complexity)が10を超えると「良い構造」と呼べなくなるそうですが、プロジェクト全体で10を超えているのは2本です。
またUI系のコードが複雑になりやすいようです。

某社内システムの場合
File Name Lines Max Complexity Max Depth
UI系.cs 1,738 47 5
UI系.cs 288 24 8
UI系.cs 1,531 20 6
UI系.cs 1,348 19 6
UI系.cs 430 14 4
ビジネスロジック系.cs 4,301 13 6
UI系.cs 1,397 12 6
UI系.cs 488 9 4
UI系.cs 426 7 5
ビジネスロジック系.cs 742 7 4


先ほどのプロジェクトに比べると複雑度が10を超えているファイルも多いです。
それだけでなく、一番複雑なファイルは30を超えているので「構造に疑問」レベルみたいです(あきぴーさんのページより)。


さらに先ほどのプロジェクトでは登場しなかった、行数が1000を超えているファイルも結構あります。
6番目に出てきているビジネスロジック系のファイルは4000行を超えています。
このファイルはこのプロジェクトにおけるほぼ唯一のビジネスロジッククラスで、いわゆる「ゴッドクラス」になってしまっています。
これはさすがにもうちょっと適切な粒度に分割すべきだったと言えるでしょう。


実はおいらが担当した改造案件の話はこの表に挙がっている一番上のファイルと、6番目に出てきている「ビジネスロジック系」のファイルに関連していました。
やはり複雑度と改造のしにくさは関係がありそうです。


ちなみにこのプロジェクトには結構大量のストアドプロシージャもあります。
これがまた分かりづらいロジックばかりで、実際の複雑度はこれよりもさらに高いのではないかと思います。

NUnitの場合
File Name Lines Max Complexity Max Depth
NUnitCore\core\PlatformHelper.cs 237 35 5
ClientUtilities\util\NUnitProject.cs 509 28 9+
ConsoleRunner\nunit-console\ConsoleUi.cs 332 27 6
PNUnit\launcher\launcher.cs 695 26 6
NUnitCore\core\NUnitFramework.cs 428 25 8
NUnitCore\core\Builders\NUnitTestCaseBuilder.cs 365 22 8
NUnitCore\core\TestMethod.cs 354 19 6
GuiComponents\UiKit\TestSuiteTreeView.cs 1,592 18 7
GuiRunner\nunit-gui\NUnitForm.cs 1,819 18 7
ClientUtilities\util\Services\DomainManager.cs 327 17 6


NUnitの場合はこんな感じです。
複雑度が10を超えているファイルも多いですが、コード行数が1000を超えているものは2本だけです。
さすがに4000を超えているものはないようですね〜。

log4netの場合
File Name Lines Max Complexity Max Depth
Repository\Hierarchy\XmlHierarchyConfigurator.cs 1,057 45 8
Util\PatternParser.cs 419 24 9+
Appender\BufferingAppenderSkeleton.cs 646 21 9+
Config\XmlConfiguratorAttribute.cs 447 20 6
Layout\XmlLayoutSchemaLog4j.cs 250 20 4
Appender\RollingFileAppender.cs 1,547 18 8
Core\DefaultRepositorySelector.cs 836 17 7
Appender\AdoNetAppender.cs 1,172 16 7
Layout\XMLLayout.cs 361 16 5
Core\LocationInfo.cs 284 13 8


全体的な傾向はNUnitに近い感じがします。
複雑度は10を超えていますが、1000行を超えているのは3本で4000行を超えているものはありません。


つづいてコードの重複率を見てみましょう。

コードの重複率

各値は以下の意味になります。

Number of files
計測対象のファイル数
Duplicate lines of code
重複しているコード行数
Total lines of code
トータルのコード行数
% Duplication (lines)
重複率
Project Number of files Duplicate lines of code Total lines of code % Duplication (lines)
開発中システム 100 121 5887 2.06%
某社内システム 22 992 6207 15.98%
NUnit 412 2879 25898 11.12%
log4net 202 626 8796 7.12%


重複しているとみなす行数を5行に設定して計測しました。
このしきい値を上げるほど重複率は下がります。


ここでもおいらの担当している開発中システムが一番低い重複率になっていますね〜(また自画自賛)。
複雑度の比較ではNUnitが低い値を出していましたが、重複率ではlog4netの方が低い値になっています。
残念ながら某社内システムはここでもワースト1になってしまっています。

まとめ

おいらはこれまで保守性や拡張性を考慮して、美しく無駄のないアーキテクチャ設計を心がけてきましたが、そのおかげか複雑度や重複率の面では著名なオープンソースプロジェクト比較しても悪くない数値を叩き出すことができました。


一方、感覚的に「複雑でメンテナンスしにくい」と思っていたプロジェクトについては数値で見た場合でも複雑度や重複率が高かったり、コード行数に妙な偏りが見られたりします。


こうしたメトリクスはプロジェクト開始時にコード品質に関する目標を設定するのに使えます。
例えば既存のプロジェクトをベースラインにするなどして、以下のような目標を設定してみるのはどうでしょうか?

  • プロジェクト全体の平均複雑度が2.0を超えないようにする
  • ファイルごとの複雑度が10を超えるような場合はコードレビューとリファクタリングを適用する
  • ファイル行数が1000を超える場合は適切な単位に分割する
  • プロジェクト全体の重複率が10%を超えないようにする (5行以上で重複と見なす)


また、既存システムの改造を行う場合は複雑度を事前にチェックしておくことで、「簡単そうに見える改造依頼だけど、複雑度が結構高いから意外と難航するかも」といったリスク管理に使えたりするかもしれません。


コードの美しさや分かりやすさ、改造のしやすさなどは感覚的に語られることが多いですが、こうしたツールを使えばある程度定量化できます。
みなさんの手元にあるソースコードもこうしたツールに通して測ってみると、色々と興味深い発見があるかもしれませんよ。

参考文献

おいらがメトリクスを測ってみようと思ったのはこの本の影響が大きいです(「第7章 継続的インスペクション」がそれ)。
今は手作業で測定していますが、理想はこの本に載っているように完全に自動化して問題を検出できるようにすることですね。


継続的インテグレーション入門 開発プロセスを自動化する47の作法

継続的インテグレーション入門 開発プロセスを自動化する47の作法