pmccabeを使って簡単にサイクロマチック複雑度を測る
サイクロマチック複雑度、あるいは循環的複雑度とは、コードのパスの複雑さを表す指標の一つだ。
10以下なら良い構造、30未満ならまあ許容、30以上なら構造的リスクあり、50以上でテスト不可能、70以上でいかなる変更もデグレを引き起こすとされている。
こんな指標を導入しなくても見る人が見ればコードの品質なんて一目瞭然だと思うけれども、世の中には「定量主義者」というような人たちがいて、特に組み込み系のシステム開発では、静的コード解析というプロセスでコードの点数化をしようとしたがるのだ。
そういう背景もあり、ちょっと微妙な感情を持って接せざるを得ない指標なのだけれども、ただ、このサイクロマチック複雑度という考え方自体はグラフ理論というきちんとした理論的背景があり、結構面白い。
要は、あまりコードの書き方によらず、そのユニットの複雑さそのものを表していると考えられるのだ。
コードの書き方というのは、つまり、if(cond1) if(cond2)とネストさせるか、if(cond1 && cond2)と書くかといった、ある程度個人の趣向に依存するところだ。
(だから実のところ、この指標で検出される問題というのは、ユニットごとのコードの書き方に依存する品質の問題ではなく、モジュール分割の不適切さといった、つまり詳細設計フェーズに帰するべきたぐいの問題なのだ…。)
このサイクロマチック複雑度というものを考え出したのはThomas J. McCabeというアメリカ人で、それは1976年の末、IEEE TRANSACTIONS ON SOFTWARE ENGINEERINGというジャーナルに"A Complexity Measure"という簡潔なタイトルの論文上で発表されたそうだ。
http://www.literateprogramming.com/mccabe.pdf
サイクロマチック複雑度を測るには、pmccabeという、この概念の発案者の名前を冠した古くからあるツールが簡便だ。
Ubuntuなら
$ sudo apt install pmccabe
でOKだし、debianのホームページからソースを持ってきて、makeでインストールすることもできる。
Debian -- Details of source package pmccabe in sid
幾つかの例で、出力を試してみる。
/* ex.c */ #include <stdio.h> void single_if (int a) { /* this is a comment */ if (a > 0) printf("a is greater than 0\n"); } void complex_if(int a, int b) { if (a > 0 && b > 0) printf("a and b are greater than 0\n"); } int condition_return(int a, int b) { return (a > 0 && b > 0); } void condition_operator(int a) { printf("a is %sgreater than 0\n", a > 0 ? "" : "NOT "); } #define ABS(x) (((x) >= 0) ? (x) : (-(x))) int use_macro(int a) { return ABS(a); } void switch_case(int a) { switch (a % 2) { case 0: printf("a is an even number\n"); break; case 1: case -1: printf("a is an odd number\n"); break; } } void single_while(int a) { while (a--) putchar('*'); } void tautology(int a) { if (a == 0 || a != 0) printf("this is always true\n"); } void tautology2(int a) { if (a == 0 || a == 1 || a < 0 || a > 1) printf("this is always true\n"); }
そして
pmccabe -v ex.c
と叩くと、次のような出力が得られる。
Modified McCabe Cyclomatic Complexity | Traditional McCabe Cyclomatic Complexity | | # Statements in function | | | First line of function | | | | # lines in function | | | | | filename(definition line number):function | | | | | | 2 2 2 4 4 ex.c(4): single_if 3 3 2 9 3 ex.c(9): complex_if 2 2 1 13 1 ex.c(13): condition_return 2 2 2 15 3 ex.c(15): condition_operator 1 1 1 20 1 ex.c(20): use_macro 2 4 4 22 8 ex.c(22): switch_case 2 2 2 31 1 ex.c(31): single_while 3 3 2 33 3 ex.c(33): tautology 5 5 2 37 3 ex.c(37): tautology2
第1フィールドと第2フィールドがサイクロマチック複雑度で、そのほかにStatementの数、関数の開始行、関数の行数、そして最後にファイル名と関数名が表示されている。
重要なのはサイクロマチック複雑度だが、これも2種類ある。
色々試したが、単純に以下の計算をしているようだ。
Modified McCabe Cyclomatic Complexity
= (ifの数) + (switchの数) + (forの数) + (whileの数) + (||の数) + (&&の数) + (?の数) + 1
Traditional McCabe Cyclomatic Complexity
= (ifの数) + (case/defaultの数) + (forの数) + (whileの数) + (||の数) + (&&の数) + (?の数) + 1
違うのはswitchを数えるか、caseを数えるかということのようだ。
Traditionalのほうがより強いメトリクスといえるだろう。
その他、上の結果からわかることとしては、
- function-likeマクロは対象にならない
- マクロが展開される前に解析される
- 恒真式(トートロジー)は判断していない
ところで、tautology()はclangでコンパイルすると
ex.c:34:16: warning: overlapping comparisons always evaluate to true [-Wtautological-overlap-compare] if (a == 0 || a != 0) printf("this is always true\n");
というWarningが出た。凄い。
けど、そのあと気になって試したtautology2()では出なかった。
やはり、トートロジーのチェックというのは難しいのだろう。