諸般の事情があり Objective-C のコードをレビューしているが、if else のネストが多く循環的複雑度を計測したくなったので計測してみた。
循環的複雑度(CCN)って?
循環的複雑度は英語で cyclomatic complexity number
であり、 CCN
と略される。
ざっくり言うと、if や switch が多いコードは分岐が多くなるので、その分岐を数字で表したもの。
CCN が 10 以下なのは良い構造であり、それ以上はよろしくないとされている。
(指標なので、絶対的にそうだというわけではない)
が、個人的に CCN が 5 を超えるとコードを追うのが辛くなってくる。(コードにもよるが)
計測してみる
OCLint で開発しながら計測するのがよさそうだが、今回はとりあえず計測だけしたかったので OCLint は使っていない。
terryyin/lizard で ObjC の CCN を計測
他にも色々とあるようだが、 GitHub で検索して出てきた terryyin/lizard を使ってみる。
Mac なら python 入っていると思うので、 pip で install できる。
$ pip install lizard
実際に計測
Pods や gem なども計測対象になるので、計測したいコードがあるディレクトリに移動して lizard コマンドを実行する。
$ cd PROJECT_ROOT/PROJECT
$ lizard -Tcyclomatic_complexity=10
================================================
NLOC CCN token PARAM length location
------------------------------------------------
3 1 19 0 4 application: didFinishLaunchingWithOptions:@18-21@./AppDelegate.m
2 1 9 0 4 applicationWillResignActive:@24-27@./AppDelegate.m
2 1 9 0 4 applicationDidEnterBackground:@30-33@./AppDelegate.m
2 1 9 0 3 applicationWillEnterForeground:@36-38@./AppDelegate.m
2 1 9 0 3 applicationDidBecomeActive:@41-43@./AppDelegate.m
2 1 9 0 3 applicationWillTerminate:@46-48@./AppDelegate.m
5 1 35 2 5 main@12-16@./main.m
3 1 8 0 4 viewDidLoad@17-20@./ViewController.m
3 1 8 0 4 didReceiveMemoryWarning@23-26@./ViewController.m
5 file analyzed.
==============================================================
NLOC Avg.NLOC AvgCCN Avg.token function_cnt file
--------------------------------------------------------------
3 0.0 0.0 0.0 0 ./AppDelegate.h
17 2.2 1.0 10.7 6 ./AppDelegate.m
5 5.0 1.0 35.0 1 ./main.m
2 0.0 0.0 0.0 0 ./ViewController.h
10 3.0 1.0 8.0 2 ./ViewController.m
=============================================================================================
No thresholds exceeded (cyclomatic_complexity > 10 or length > 1000 or parameter_count > 100)
==========================================================================================
Total nloc Avg.NLOC AvgCCN Avg.token Fun Cnt Warning cnt Fun Rt nloc Rt
------------------------------------------------------------------------------------------
37 2.7 1.0 12.8 9 0 0.00 0.00
作成して何もしていない Project なので問題のあるコードはないと出ている。
if 文を大量に置いて計測
精神が不安定になるコード:
- (void)viewDidLoad {
[super viewDidLoad];
if (true) {
if (true) {
if (true) {
if (true){
if (true){
if (true){ }
}
}
}
}
} else {
if (true) { }
}
if (true) { }
if (true) { }
if (true) { }
if (true) { }
}
lizard を実行
$ lizard -Tcyclomatic_complexity=10
... 略 ...
=========================================================================================
!!!! Warnings (cyclomatic_complexity > 10 or length > 1000 or parameter_count > 100) !!!!
================================================
NLOC CCN token PARAM length location
------------------------------------------------
21 12 77 0 22 viewDidLoad@17-38@./ViewController.m
CCN が 12 になりました、Warning で教えてくれています。
コードの品質を計測することができました
(ただし、 CCN はあくまで一つの指標です)
こうならないために
早期リターンをしたり、メソッドの分割をしてネストを浅くしていきましょう
おまけ
本当に 10 以下なら問題ないと言えるのか?
このコードで CNN は 7 、だがどう見てもこれは...
- (void)viewDidLoad {
[super viewDidLoad];
if (true) {
if (true) {
if (true) {
if (true){
if (true){
if (true){ }
}
}
}
}
}
}
Swift も計測できる
いま仕事で書いているコード。
SwiftLint で設定しているのでそれと同じ結果だが、 Swift の場合 enum の switch case 増えるしそこで CCN 高くなると思うのだが皆どうしているんだろう。
========================================================================================
!!!! Warnings (cyclomatic_complexity > 5 or length > 1000 or parameter_count > 100) !!!!
================================================
NLOC CCN token PARAM length location
------------------------------------------------
18 15 127 1 18 hoge@134-151@./Hoge/Error.swift
30 8 142 1 31 hoge@28-58@./Hoge/Message.swift
11 6 117 0 11 hoge@59-69@./Hoge/Controller.swift