gitで差分のあったファイルに対してのみ循環的複雑度(CCN)を計測する

[PONOS Advent Calendar 2022](https://qiita.com/advent-calendar/2022/ponos)の7日目の記事です。
昨日は@nissy_gpさんの「[エンジニアの仕事を不確実性という原点から考えてみる](https://qiita.com/nissy_gp/items/2373d16cb156cbe0a02e)」でした。

# はじめに

先日の記事「[Unityエディタ上で循環的複雑度(CCN)を計測する](https://qiita.com/e73ryo/items/e50b7c90a072bbd615c8)」ではlizardというプログラムを用いて、Unityエディタ上でソースコード中の循環的複雑度（以下、CCN）を計測する方法を紹介しました。

この記事では、また少し違ったCCN計測の手法をご紹介します。
今回は、**2つのブランチの差分を見て、実際に変更のあったソースコードのCCN値を取得する方法**をご紹介します。
コードの細部は異なりますが現在参加しているプロジェクトにも導入している仕組みであり、プルリクエストが出されたタイミングでCCNの計測を行ってソースコード毎の複雑度をチェックすることで、保守性の低いソースコードや変更に気づかずにマージされてしまう事態を防いでいます。


# 前提

以下の2ファイルを管理しているプロジェクトがあるとします。

```csharp:Sample1.cs
using UnityEngine;

public class Sample1 : MonoBehaviour
{
    void Start()
    {
    }
}
```

```csharp:Sample2.cs
using UnityEngine;

public class Sample2 : MonoBehaviour
{
    void Start()
    {
    }
}
```

これらのファイルは同じフォルダに置かれ、`main`という名前のブランチの中で管理されています。
`main`ブランチは開発の本流のブランチを指しています。

次に、`main`ブランチから分岐した`work`ブランチを作成します。
この`work`ブランチは実際の変更をコミットするブランチを指しています。

次に、`work`ブランチ上で`Sample1.csだけを以下のように変更しコミットします。
（CCN値の変化を確認するために分岐を適当に増やしています）

```csharp:Sample1.cs
using UnityEngine;

public class Sample1 : MonoBehaviour
{
    void Start()
    {
        if(true) // まったく意味のない分岐。
        {
        }
    }
}
```

`main`ブランチと`work`ブランチは、以下のような運用をイメージしています。
```
1. mainブランチから作業用のブランチ（work）を作成
　↓
2. workブランチ上で変更をコミット
　↓
3. mainブランチに向けて、workブランチのプルリクエストを作成する
（このタイミングでCCNの高い変更があれば検知したい）
```

上記3でCCNの計測を行いってその結果を出力させることができれば、要件は達成できそうです。
しかし、そのまま何も考えずにlizardでプロジェクト内を計測してしまうと、「プルリクエストに含まれていない（＝変更されていない）ソースコード」に対しても計測が実施され、計測結果のノイズとなってしまいます。
そこで、ここでは、**プルリクエストで変化のあったソースコードに限定してCCNを計測する**具体的なコマンドを紹介していきます。

# gitで差分のあったファイルに限定してCCN計測するコマンド

コマンドはかなりシンプルです。

```shell
$ git checkout work

$ TARGETS=`git diff main...work --name-only`

$ lizard --csv $TARGETS
```

分解して順番に見ていきます。
まずは、`git checkout`コマンドを使って、「変更をコミットしたブランチ（`work`）」に切り替えています。
今回の変更で複雑度の高いソースコードが無いかを判定するために、変更後のソースコードに対してlizardを実行する必要があるためです。

```shell
$ git checkout work
Switched to branch 'work'
```

続いて、`git diff`コマンドを使い、特定の２つのブランチの差分情報を取得し、変数`$TARGETS`に格納しています。
変数`$TARGETS`には変更のあったソースコードのファイル名が格納され、この変数をlizardに渡すことで計測対象を限定させます。

```shell
$ TARGETS=`git diff source...destination --name-only`
```

`git diff`は`--name-only`オプションを追加することで、差分のあるファイル名だけを出力させることができます。

```shell:--name-onlyを指定しない場合
$ TARGETS=`git diff source...destination`
$ echo $TARGETS
diff --git a/Sample1.cs b/Sample1.cs index 60aa9dc..073da88 100644 --- a/Sample1.cs +++ b/Sample1.cs @@ -4,5 +4,8 @@ public class Sample1 : MonoBehaviour { void Start() { + if(true) // 意味のない分岐 + { + } } }
```
```shell:--name-onlyを指定した場合
$ TARGETS=`git diff source...destination --name-only`
$ echo $TARGETS
Sample1.cs
```

今回はlizardに計測対象のファイル名を渡すことが目的ですので、`--name-only`オプションを指定しています。

最後に、変数$TARGETSをlizardへ渡して計測を実行し、今回変更されたファイルのCCN値を確認します。
lizardはオプションでファイル名 / フォルダ名を渡すことで、計測の範囲を限定することができます。

```shell
$ lizard --csv $TARGETS
```

ここで指定している`--csv`オプションは、lizardの実行結果の出力形式です。
得られる結果は同じですが、csv形式で出力されているほうが後の処理でデータを利用しやすいためオススメです。

```shell:--csvを指定しない場合
$ lizard $TARGETS
================================================
  NLOC    CCN   token  PARAM  length  location
------------------------------------------------
       6      2     11      0       6 Sample1::Start@5-10@Sample1.cs
1 file analyzed.
==============================================================
NLOC    Avg.NLOC  AvgCCN  Avg.token  function_cnt    file
--------------------------------------------------------------
     10       6.0     2.0       11.0         1     Sample1.cs

===============================================================================================================
No thresholds exceeded (cyclomatic_complexity > 15 or length > 1000 or nloc > 1000000 or parameter_count > 100)
==========================================================================================
Total nloc   Avg.NLOC  AvgCCN  Avg.token   Fun Cnt  Warning cnt   Fun Rt   nloc Rt
------------------------------------------------------------------------------------------
        10       6.0     2.0       11.0        1            0      0.00    0.00
```

```shell:--csvを指定した場合
$ lizard --csv $TARGETS
6,2,11,0,6,"Sample1::Start@5-10@Sample1.cs","Sample1.cs","Sample1::Start","Sample1::Start()",5,10
```

この計測結果を参考に、プルリクエストをそのままマージしてもよいのか判断しましょう。


# まとめ

前回に引き続き、CCNを計測する方法について紹介しました。
皆様のソースコードの保守性を保つためのヒントになれば幸いです。

明日は@ackylaさんです！