CohesionでPythonの凝集度を計測する

Pythonでの凝集度の測り方

## 凝集度とは
凝集度はソフトウェアのメトリクスで、関連するメソッドや変数が近くに置かれているかを測る指標です。

例えば[LCOM4](https://objectscriptquality.com/docs/metrics/lack-cohesion-methods-lcom4)だと、とあるクラスにおいて変数にアクセスしている関数を辿っていってクラスタリングして、クラスタ数が1なら良い状態、2以上ならクラスを分割すべきなので悪い状態、0(メソッドなし)も悪い状態としています。

今回、Pythonで凝集度を測れる[cohesion](https://github.com/mschwager/cohesion)をみつけたので試してみます。

cohesionでの凝集度の定義はとあるクラスにおいて、とあるメソッドが全てのインスンス変数、クラス変数を使用していると100%としているようです。

## 環境構築
実際のコードベースに対して評価したいのでPython製のチャットサービスのZulipのコードを持ってきます。

依存を見る限りDjangoとTornadoを両方使用しているようです。複雑。

```bash
$ git clone --depth 1 git@github.com:zulip/zulip.git
# 1e0339c18bb46a1c502b01a71c2d66471848cf36
```

cohesionのインストール

```bash
$ python -m pip install cohesion
$ python -m cohesion -h
usage: __main__.py [-h] [-v | -x] (-f FILES [FILES ...] | -d DIRECTORY) [-b BELOW | -a ABOVE]

        A tool for measuring Python class cohesion.


optional arguments:
  -h, --help            show this help message and exit
  -v, --verbose         print more verbose output
  -x, --debug           print debugging output
  -f FILES [FILES ...], --files FILES [FILES ...]
                        analyze these Python files
  -d DIRECTORY, --directory DIRECTORY
                        recursively analyze this directory of Python files
  -b BELOW, --below BELOW
                        only show results with this percentage or lower
  -a ABOVE, --above ABOVE
                        only show results with this percentage or higher
```

## Zulipにcohesionをかけてみる
Zulipにcohesionをかけています。

DjangoはMVCでなくMTCなのでざっくりMVCのModel=DjangoのModel, MVCのView = DjangoのTemplate, MVCのController=DjangoのView。

ロジックがありそうなのはmodelとviewだとあたりをつけて1ファイルについてcohesionをかけてみます。

```bash
$ python -m cohesion -f zerver/models/alert_words.py
File: zerver/models/alert_words.py
  Class: AlertWord (14:0)
    Total: 0.0%
  Class: Meta (24:4)
    Total: 0.0%

$ python -m cohesion -f zerver/views/auth.py
File: zerver/views/auth.py
  Class: TwoFactorLoginView (794:0)
    Function: __init__ 1/3 33.33%
    Function: get_context_data 2/3 66.67%
    Function: done 0/3 0.00%
    Total: 33.33%
```

定義に従って、ダブルアンダースコアのメソッドも対象になります。

`__init__` はまだ許せるのですが、 `__str__` が出る場合もあるのでクラス設計の良さの指標の要素に入れてしまうのはノイズな気もしますね。

## ディレクトリ指定で一括測定
続いて -d でディレクトリ一括でcohesionをかけます。

```bash
$ python -m cohesion -d zerver/models/
File: zerver/models/custom_profile_fields.py
  Class: CustomProfileField (59:0)
    Function: __str__ 4/20 20.00%
    Function: as_dict 7/20 35.00%
    Function: is_renderable 1/20 5.00%
    Total: 20.0%
  Class: CustomProfileFieldValue (182:0)
    Function: __str__ 3/4 75.00%
    Total: 75.0%
  Class: Meta (188:4)
    Total: 0.0%
File: zerver/models/linkifiers.py
  Class: RealmFilter (48:0)
    Function: __str__ 3/4 75.00%
    Function: clean 2/4 50.00%
    Total: 62.5%
  Class: Meta (60:4)
    Total: 0.0%
File: zerver/models/onboarding_steps.py
  Class: OnboardingStep (8:0)
    Total: 0.0%
  Class: Meta (13:4)
    Total: 0.0%
(後略)

$ python -m cohesion -d zerver/views
File: zerver/views/registration.py
File: zerver/views/user_settings.py
File: zerver/views/digest.py
File: zerver/views/auth.py
  Class: TwoFactorLoginView (794:0)
    Function: __init__ 1/3 33.33%
    Function: get_context_data 2/3 66.67%
    Function: done 0/3 0.00%
    Total: 33.33%
File: zerver/views/custom_profile_fields.py
File: zerver/views/realm_linkifiers.py
File: zerver/views/documentation.py
  Class: DocumentationArticle (35:0)
    Total: 0.0%
  Class: ApiURLView (66:0)
    Function: get_context_data 1/1 100.00%
    Total: 100.0%
  Class: MarkdownDirectoryView (78:0)
    Function: get_path 4/5 80.00%
    Function: get_context_data 4/5 80.00%
    Function: get 2/5 40.00%
    Total: 66.67%
  Class: IntegrationView (323:0)
    Function: get_context_data 1/2 50.00%
    Total: 50.0%
(後略)
```

別でactionsというディレクトリも含めてかけてみたのですが、全体的にクラスのないファイルが多いですね。。。

コードの品質メトリクスはJava由来のクラスベースの思想を基準にしていることが多いのですが、クラスをあまり使わないアーキテクチャだと流用はできないですね。

## 上位、下位のファイルの洗い出し
-bで一定以下のファイル、-aで一定以上のファイルを出せます。

前述の通り、クラスのないファイルがだいぶノイズになるので適当にフィルターしています。

```bash
$ python -m cohesion -d zerver/views -b 30 |grep -v 'File:'
  Class: DocumentationArticle (35:0)
    Total: 0.0%
  Class: VideoCallSession (39:0)
    Function: __init__ 0/0 0.00%
    Total: 0.0%
  Class: InvalidZoomTokenError (44:0)
    Function: __init__ 0/1 0.00%
    Total: 0.0%
  Class: InvalidMirrorInputError (29:0)
    Total: 0.0%
  Class: SentryTunnelSession (25:0)
    Function: __init__ 0/0 0.00%
    Total: 0.0%

$ python -m cohesion -d zerver/views -a 70 |grep -v 'File:'
  Class: ApiURLView (66:0)
    Function: get_context_data 1/1 100.00%
    Total: 100.0%
```

## 最後に
以上です。

お手軽に凝集度を測れるツールとしては有用なんですが、Zulipのつくりだとそこまであっていないといった感じでしょうか。

ライブラリーのコードも比較的短いので自身のユースケースに合わせて自作してみるのも面白そうですね。