Objective-C の循環的複雑度を計測する

諸般の事情があり Objective-C のコードをレビューしているが、if else のネストが多く循環的複雑度を計測したくなったので計測してみた。

## 循環的複雑度(CCN)って？

循環的複雑度は英語で `cyclomatic complexity number` であり、 `CCN` と略される。

ざっくり言うと、if や switch が多いコードは分岐が多くなるので、その分岐を数字で表したもの。
CCN が 10 以下なのは良い構造であり、それ以上はよろしくないとされている。
(指標なので、絶対的にそうだというわけではない)

が、個人的に CCN が 5 を超えるとコードを追うのが辛くなってくる。(コードにもよるが)

* 参考
    * [循環的複雑度を活かしたバグ潜在リスクの軽減 \- 現場のためのソフトウェア開発プロセス \- たかのり日記](http://szk-takanori.hatenablog.com/entry/20111219/p1)
    * [循環的複雑度 \- Wikipedia](https://ja.wikipedia.org/wiki/%E5%BE%AA%E7%92%B0%E7%9A%84%E8%A4%87%E9%9B%91%E5%BA%A6)

## 計測してみる

[OCLint](http://oclint.org/) で開発しながら計測するのがよさそうだが、今回はとりあえず計測だけしたかったので OCLint は使っていない。

### [terryyin/lizard](https://github.com/terryyin/lizard) で ObjC の CCN を計測

他にも色々とあるようだが、 GitHub で検索して出てきた [terryyin/lizard](https://github.com/terryyin/lizard) を使ってみる。

Mac なら python 入っていると思うので、 pip で install できる。

```bash
$ pip install lizard
```

### 実際に計測

Pods や gem なども計測対象になるので、計測したいコードがあるディレクトリに移動して lizard コマンドを実行する。

```bash
$ 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 文を大量に置いて計測

精神が不安定になるコード:

```objc
- (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 を実行

```bash
$ 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 で教えてくれています。

コードの品質を計測することができました :tada:
(ただし、 CCN はあくまで一つの指標です)

### こうならないために

早期リターンをしたり、メソッドの分割をしてネストを浅くしていきましょう :muscle:

## おまけ

### 本当に 10 以下なら問題ないと言えるのか？

このコードで CNN は 7 、だがどう見てもこれは... :scream: 

```objc
- (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
```