こんにちは。最近よくソフトウェアの品質の改善について考えています。
@Esperna です。今回はサイクロマティック複雑度について書きます。
背景
ソフトウェアの品質を改善しようと思った時にソフトウェアの品質を改善するにも、
そもそもソフトウェアの品質の定義って何なのだろうと思いました。
定性的にはコードにバグが多い・少ないとか、コードの読みにくい・読み易いとか?
定量的にはソフトウェアメトリクスとか?
ソフトウェアメトリクスにはサイクロマティック複雑度、SLOC、関数のネストの深さなどがあるかなと思うのですが、Microsoftが言及しているMaintainability Indexというものがよく使われているようです。
Maintainability Index = 171 - 5.2 * ln(Halstead Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(Lines of Code)
これは式だけ見ると小難しく見えるのですが、対数取ってるのは
スケール調整してるだけと思われるのと、
細かい係数も重み付けのための係数と思われるのでそちらはあまり気にせず。
Maintainability Index(保守容易性指数)というのは
- Halstead Volumeが増えると下がる(減ると上がる)
- Cyclomatic Complexity(サイクロマティック複雑度)が増えると下がる(減ると上がる)
- SLOC(コード行数)が増えると下がる(減ると上がる)
と言っていると理解しました。
となると、Halstead VolumeとCyclomatic Complexity(サイクロマティック複雑度)が分かれば
Maintainability Index(保守容易性指数)を完全理解できる!
というわけで、まずはCyclomatic Complexity(サイクロマティック複雑度)をググりました。
サイクロマティック複雑度
結論から言うと、実用上はコードの分岐の数(分岐網羅数)に1を足したものと理解するのが良さそうです。
循環複雑度の定義によると、
M = E − N + 2P
ここで
M = 循環的複雑度
E = グラフのエッジ数
N = グラフのノード数
P = 連結成分の個数
グラフ、エッジ、ノード、連結成分の定義が分からないので調べてみると、グラフ理論で定義される用語らしいです。
- ノード:頂点(下図で言うところの○)
- エッジ:ノードをつなぐ線(下図で言うところの-)
- グラフ:ノードとエッジの集合
- 連結成分:部分グラフのうち極大で連結なもの。関数の場合、連結成分の数は1。
仮に下記のような処理を行うコードがあった場合に
if (isXXXEnable) {
//trueの場合の処理
} else {
//falseの場合の処理
}
if (isYYYEnable) {
//trueの場合の処理
} else {
//falseの場合の処理
}
グラフにしてみると以下の通りで
E(グラフのエッジの数) = 8
N(グラフのノードの数) = 7
P(連結成分の数) = 1
なので、
M = E - N + 2P
= 8 - 7 + 2
= 3
この M = 3というのは分岐網羅数2より大きく経路網羅数4より小さいです。
結局サイクロマティック複雑度Mは何者かというと、コードの分岐の数(分岐網羅数)に1を足したものということになリます。なお、Halstead Volumeはプログラム全体をビット配列で表現したときの長さだそうです(参考資料参照)。
所感
- サイクロマティック複雑度が上がると、分岐の数が増えて、Maintainabilityが下がりますよねということだと理解しました。
- 調べていくうちに 保守容易性指数(Maintainability Idex)の計算方法を理解するという記事が書かれていることを知り、車輪の再発明だったなと。でも、自身の理解を深める目的で書いていたのでそれでもいいかなと思いました。
- 行数が長く、分岐の数が多く、プログラム全体をビット配列で表現したときの長さが長いコードはMaintainabilityが悪いということですね。
- 上記のMaintainabilityの意味を踏まえると、当たり前ですが追加実装を行えばリファクタリングしたとしてもMaintainabilityは下がることが多そうですね(まさにコード負債だなと思いました)。