サイクロマティック複雑度の計測ツール「lizard」のセットアップ&使い方

## 「lizard」とは？

コマンドラインのサイクロマティック複雑度(以下、CCN)計測ツールです。
OSSなのでフリーで使えます。

CCNについては[別記事](https://qiita.com/uhooi/items/c77a53a4c7ac232a1ba1)で説明しているので、そちらをご参照ください。

## 環境

- OS：macOS High Sierra 10.13.1
- Python：3.6.1
- pip：9.0.1
- lizard：1.14.10
- jinja2：2.10

## セットアップ

### Python環境の構築

lizardはPython製なので、Pythonのインストールが必要です。
Python環境の構築については[こちらの記事](https://qiita.com/uhooi/items/b2f3a121b2e9dac6a256#開発環境構築)をご参照ください。

私は以下のようなPython環境を作成しました。

```bash
$ pyenv virtualenv 3.6.1 CCNAnalyzer
```

### lizardのインストール

pipでインストールします。

```bash
$ pip install lizard
Collecting lizard
  Using cached https://files.pythonhosted.org/packages/25/78/4fad2f578ce5b42f9f635a27f81d1332183e9f14b506a158e298ec759f03/lizard-1.14.10-py2.py3-none-any.whl
Installing collected packages: lizard
Successfully installed lizard-1.14.10
You are using pip version 9.0.1, however version 10.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
```

pipが古いと怒られましたが、無事にインストールできました。

## 操作方法

### 標準出力

今回は[SwiftLintのソース](https://github.com/realm/SwiftLint/tree/master/Source/swiftlint)をお借りして、CCNを計測してみます。

`-l` オプションの値は言語に応じて変更してください。

```bash
# プロジェクトのルートフォルダへ移動する
$ cd SwiftLint/Source/swiftlint/

# CCNを計測する
$ lizard -l swift
================================================
  NLOC    CCN   token  PARAM  length  location
------------------------------------------------
       8      3     38      2       8 report@12-19@./Extensions/Reporter+CommandLine.swift
       4      2     39      2       4 reporterFrom@22-25@./Extensions/Reporter+CommandLine.swift
      34      6    238      0      36 scriptInputFiles@16-51@./Extensions/Configuration+CommandLine.swift
       1      1     14      1       1 autoreleasepool@54-54@./Extensions/Configuration+CommandLine.swift
      55      8    400      8      55 visitLintableFiles@59-113@./Extensions/Configuration+CommandLine.swift
      17      6    136      5      17 getFiles@115-131@./Extensions/Configuration+CommandLine.swift
       7      2     74      1       7 init@135-141@./Extensions/Configuration+CommandLine.swift
       6      1     80      3       6 visitLintableFiles@143-148@./Extensions/Configuration+CommandLine.swift
       6      2     68      1       6 init@152-157@./Extensions/Configuration+CommandLine.swift
       4      1     34      1       4 init@161-164@./Extensions/Configuration+CommandLine.swift
      14      3     79      1      15 print@19-33@./Commands/RulesCommand.swift
      16      5    138      1      20 run@39-58@./Commands/RulesCommand.swift
      17      7    107      2      21 ruleList@60-80@./Commands/RulesCommand.swift
       8      2     73      1       8 create@90-97@./Commands/RulesCommand.swift
      12      1     88      1      12 evaluate@99-110@./Commands/RulesCommand.swift
      40      6    275      2      40 init@116-155@./Commands/RulesCommand.swift
       9      1     51      0       9 currentWidth@159-167@./Commands/RulesCommand.swift
      48      8    392      1      48 run@20-67@./Commands/LintCommand.swift
       9      3     50      2       9 successOrExit@69-77@./Commands/LintCommand.swift
       9      2     51      3       9 printStatus@79-87@./Commands/LintCommand.swift
       6      1     51      2       6 isWarningThresholdBroken@89-94@./Commands/LintCommand.swift
      13      1     65      1      13 createThresholdViolation@96-108@./Commands/LintCommand.swift
      15      3     78      2      15 applyLeniency@110-124@./Commands/LintCommand.swift
       5      1    185      1       5 create@142-146@./Commands/LintCommand.swift
      24      1    225      1      25 evaluate@148-172@./Commands/LintCommand.swift
       4      1     43      1       4 run@17-20@./Commands/VersionCommand.swift
      25      6    206      1      26 run@17-42@./Commands/AutoCorrectCommand.swift
       5      1    125      1       5 create@56-60@./Commands/AutoCorrectCommand.swift
      17      1    143      1      18 evaluate@62-79@./Commands/AutoCorrectCommand.swift
      13      3     90      1      15 run@17-31@./Commands/GenerateDocsCommand.swift
       3      1     20      1       3 create@37-39@./Commands/GenerateDocsCommand.swift
       6      1     47      1       6 evaluate@41-46@./Commands/GenerateDocsCommand.swift
       3      1     13      1       3 init@21-23@./Helpers/Benchmark.swift
       3      1     27      2       3 record@25-27@./Helpers/Benchmark.swift
       3      1     30      2       3 record@29-31@./Helpers/Benchmark.swift
      14      2    153      0      15 save@33-47@./Helpers/Benchmark.swift
       5      1     28      1       5 pathOption@12-16@./Helpers/CommonOptions.swift
       6      1     30      1       6 quietOption@27-32@./Helpers/CommonOptions.swift
10 file analyzed.
==============================================================
NLOC    Avg.NLOC  AvgCCN  Avg.token  function_cnt    file
--------------------------------------------------------------
     15       6.0     2.5       38.5         2     ./Extensions/Reporter+CommandLine.swift
    140      16.2     3.4      130.5         8     ./Extensions/Configuration+CommandLine.swift
    139      16.6     3.6      115.9         7     ./Commands/RulesCommand.swift
    153      16.1     2.5      137.1         8     ./Commands/LintCommand.swift
     11       4.0     1.0       43.0         1     ./Commands/VersionCommand.swift
     64      15.7     2.7      158.0         3     ./Commands/AutoCorrectCommand.swift
     32       7.3     1.7       52.3         3     ./Commands/GenerateDocsCommand.swift
     44       5.8     1.2       55.8         4     ./Helpers/Benchmark.swift
     20       5.5     1.0       29.0         2     ./Helpers/CommonOptions.swift
     17       0.0     0.0        0.0         0     ./main.swift

=============================================================================================
No thresholds exceeded (cyclomatic_complexity > 15 or length > 1000 or parameter_count > 100)
==========================================================================================
Total nloc   Avg.NLOC  AvgCCN  Avg.token   Fun Cnt  Warning cnt   Fun Rt   nloc Rt
------------------------------------------------------------------------------------------
       635      13.0     2.6      104.8       38            0      0.00    0.00
```

メソッドごとのCCN→ファイルごとのCCN平均→全ファイルのCCN平均 の順で出力されます。

「メソッドごとのCCN」について、各列の意味は以下の通りです。

|列|略|説明|
|:---|:---|:---|
|NLOC|Number Line Of Code|コードの行数(コメントを除く)|
|CCN|Cyclomatic Complexity Number|サイクロマティック複雑度|
|token|-|トークン（字句）数|
|PARAM|PARAMeter|メソッドの引数の数|
|length|-|NLOCから空行を除いた行数|
|location|-|{メソッド名}@{開始行}-{終了行}@{ファイルパス}|

ファイルごとや全ファイルについての出力もほぼ同様です。

出力結果の見方がわかったところで流石のSwiftLintです。CCNが全メソッドで1桁に収まっています。

### CSV出力

標準出力だと使いづらいので `lizard --help` でオプションを確認したところ、他にも様々な形式で出力できることがわかりました。

`--csv`オプションを付けることで、CSV形式で出力されます。
Python環境が構築されていることですし、pandasやmatplotlibを使ってグラフ化するのもよさそうです。

```bash
# 出力結果をCSVファイルへ出力する
$ lizard -l swift --csv > ~/Desktop/SwiftLint_CCN.csv
```

#### 出力結果

CSVの場合はメソッドごとのCCNのみ出力されるようです。
ヘッダー(1行目)は出力されなかったので、公式のGitHubなどを参考にして付けました。

```csv:SwiftLint_CCN.csv
nloc,ccn,token_count,parameter_count,length,location,filepath,method_name,method_long_name,start_line,end_line
8,3,38,2,7,"report@12-19@./Extensions/Reporter+CommandLine.swift","./Extensions/Reporter+CommandLine.swift","report","report violations : [ StyleViolation ] , realtimeCondition : Bool",12,19
4,2,39,2,3,"reporterFrom@22-25@./Extensions/Reporter+CommandLine.swift","./Extensions/Reporter+CommandLine.swift","reporterFrom","reporterFrom options : LintOptions , configuration : Configuration",22,25
34,6,238,0,35,"scriptInputFiles@16-51@./Extensions/Configuration+CommandLine.swift","./Extensions/Configuration+CommandLine.swift","scriptInputFiles","scriptInputFiles",16,51
…
6,1,30,1,5,"quietOption@27-32@./Helpers/CommonOptions.swift","./Helpers/CommonOptions.swift","quietOption","quietOption action : String",27,32
```

### HTML出力

`--html` オプションを付けることで、HTML形式で出力されます。

```bash
# 出力結果をHTMLで出力する
$ lizard -l swift --html > ~/Desktop/SwiftLint_CCN.html
HTML Output depends on jinja2. `pip install jinja2` first
```

出力に失敗しました。
HTML形式で出力する場合、jinja2というツールも必要とのことです。

lizardと同じくpipでインストールします。

```bash
$ pip install jinja2
```

これで再度コマンドを実行するとHTMLで出力されます。

#### 出力結果

標準出力やCSVよりグラフィカルで見やすいです。

CSVと同様、メソッドごとのCCNのみ出力されるようです。

<img width="728" alt="スクリーンショット 2018-04-22 18.03.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/138245/dafe6fb9-a30e-88a8-7ba0-e7d9e29466f8.png">

SwiftLintは優秀なので出力されませんでしたが、CCNがしきい値(デフォルトだと15)を超えると[Cyclomatic complexity]列の背景色が緑から赤に変わります。

## 参考リンク

- [‎terryyin/lizard: A simple code complexity analyser without caring about the C/C++ header files or Java imports, supports most of the popular languages.](https://github.com/terryyin/lizard)
- [Objective-C の循環的複雑度を計測する - Qiita](https://qiita.com/star__hoshi/items/4db8c8c09b05af3143d1)
- [lizardでMcCabeの循環的複雑度を測る - Qiita](https://qiita.com/n_morioka/items/69f59c359bbd15db7821)