Pythonでの凝集度の測り方
凝集度とは
凝集度はソフトウェアのメトリクスで、関連するメソッドや変数が近くに置かれているかを測る指標です。
例えばLCOM4だと、とあるクラスにおいて変数にアクセスしている関数を辿っていってクラスタリングして、クラスタ数が1なら良い状態、2以上ならクラスを分割すべきなので悪い状態、0(メソッドなし)も悪い状態としています。
今回、Pythonで凝集度を測れるcohesionをみつけたので試してみます。
cohesionでの凝集度の定義はとあるクラスにおいて、とあるメソッドが全てのインスンス変数、クラス変数を使用していると100%としているようです。
環境構築
実際のコードベースに対して評価したいのでPython製のチャットサービスのZulipのコードを持ってきます。
依存を見る限りDjangoとTornadoを両方使用しているようです。複雑。
$ git clone --depth 1 git@github.com:zulip/zulip.git
# 1e0339c18bb46a1c502b01a71c2d66471848cf36
cohesionのインストール
$ 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をかけてみます。
$ 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をかけます。
$ 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で一定以上のファイルを出せます。
前述の通り、クラスのないファイルがだいぶノイズになるので適当にフィルターしています。
$ 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のつくりだとそこまであっていないといった感じでしょうか。
ライブラリーのコードも比較的短いので自身のユースケースに合わせて自作してみるのも面白そうですね。