本日、Gry v0.3.1 をリリースしました。
このリリースで Metrics Cop の設定を自動的に行えるようになったので、機能の紹介をします。
Gry とは
Gry は Generate .Rubocop.Yml の略で、プロジェクトに最適化された.rubocop.yml
(RuboCop の設定ファイル)を自動生成する為のツールです。
RuboCop には多くの Cop(ルール)があり、またそれぞれの Cop にはいくつかの設定があります。
それらをプロジェクトに合わせて適切に設定するのは非常に骨が折れる作業ですが、Gry を使うことでそれを自動化することが可能です。
詳しくは GitHub レポジトリや Gry についての紹介記事をご覧ください。
隠れたコーディングスタイルを .rubocop.yml に抽出するツール、Gryをリリースしました! - SideCI Blog
また、設計思想に関してはこちらの記事で述べていますので、ご興味がありましたらご覧ください。
Gry の設計思想と実装について - Qiita
Metrics Cop とは
Metrics Cop とは、RuboCop にある Cop のうちメトリクス系の Cop を示し、主に複雑すぎるコードを検出します。
Metrics/MethodLength
がわかりやすいので、これを例に解説をします。
Metrics/MethodLength
cop は、メソッドに含まれる行数を単純に計測します。
例えば以下のfoo
メソッドの場合、Metrics/MethodLength
の値は 3 となります。
def foo
if cond
foobar
end
end
そして、RuboCop はこの値が一定のしきい値を超えた際に警告を出します。
Metrics/MethodLength
のデフォルトのしきい値は 10 になっており、すなわち1つのメソッドの行数が11行以上であると警告が発生します。
他にもいくつかある Metrics Cop も同様の仕組みとなっており、それぞれ分岐の数などの指標を用いて同様にコードの複雑度を検出します。
適切なしきい値
では、このしきい値は適切なものなのでしょうか?
多くの場合において、このしきい値は厳しすぎるものになっていると言えるでしょう。
しきい値の現状
例えば RuboCop 自身は RuboCop の設定を殆どデフォルトのまま使用していますが、一部の Metrics 系の Cop は設定を緩めた上でそれを使用しています。
https://github.com/bbatsov/rubocop/blob/573cf0d06d8557884e6956bf6c4d862f7ef0065a/.rubocop_todo.yml
また、その他にも多くのプロジェクトで Metrics Cop のしきい値が緩められており、また無効にされているものも存在します。
https://github.com/sideci/meowcop/blob/5cc9aef9a121dcacb98e1c0bc87ce1a32e7d0192/config/rubocop.yml#L37-L69
https://github.com/onk/onkcop/blob/1f07d4e09856c507ea7dfdd8ac5496c21d4717b5/config/rubocop.yml#L247-L283
https://github.com/gitlabhq/gitlabhq/blob/0fddece769352b650c01d3a0920eea05f378b940/.rubocop.yml#L474-L517
しきい値を緩めることの問題
ですが、しきい値をむやみに緩めることには問題があります。
例えば RuboCop 自身では警告が全く出ない状態を維持するため、一部の Metrics 系 Cop のしきい値を一番複雑なに合わせて緩めています。
この状況では一番複雑なメソッドやクラスを許容する設定がなされているため、実質的にその Cop は機能しません。
例えば「複雑だけど最悪な複雑さではないメソッド/クラス」が出てきた場合、最悪な複雑さを持つメソッド/クラスにそれが隠れてしまい、検出することが不可能となってしまいます。
また、他の多くのプロジェクトでも基準が不明瞭なまましきい値が緩められていると考えられます。
実際に MeowCop では、「最悪だ」と感じられるコードだけを検出していると感じられるしきい値を感覚的に選んでいます。
Gry のアプローチ
これに対して Gry では、明確な基準を持ってしきい値を選択する仕組みを実装しました。
多くのプロジェクトにおいて、ごく一部のクラス/メソッドがものすごく複雑であるが、その他の大多数はそこまで複雑ではない、という状況が考えられます。
この状態で一番複雑なものに対してのしきい値を設定した場合、先述の通り「ちょっと複雑すぎるクラス/メソッド」を見逃してしまうことになります。
それを回避するため、95パーセンタイルのコードを許可し、上位5パーセンタイルのコードのみに警告を出すような.rubocop.yml
を生成する機能を Gry に追加しました。
これにより、明確な基準によりコードの複雑度を検査することが出来るようになります。
Installation
Metrics Cop の設定を生成するには、Gry v0.3.1 以上が必要です。
$ gem install gry
$ gry --version
gry 0.3.1 (RuboCop 0.47.1 (using Parser 2.3.3.1, running on ruby 2.4.0 x86_64-linux))
Usage
通常通り Gry を実行することで、Metrics Cop の設定も他の Cop と同様に生成されます。
$ gry >> .rubocop.yml
また、Metrics Cop のみの設定を行いたい場合、明示的に Cop 名を指定する必要があります。
大きいプロジェクトでは Gry の実行には時間がかかりますが、Metrics Cop のみを指定した場合は比較的短時間で実行が完了します。
$ gry Metrics/AbcSize Metrics/BlockLength Metrics/ClassLength Metrics/CyclomaticComplexity Metrics/LineLength Metrics/MethodLength Metrics/ModuleLength Metrics/PerceivedComplexity >> .rubocop.yml
尚、デフォルトでは95パーセンタイルのコードを許可するようになっていますが、この値は--metrics-percentile
オプションで変更することが出来ます。
# 98パーセンタイルのコードを許可する
$ gry --metrics-percentile=98 >> .rubocop.yml
実行例
実際に弊社のレポジトリに対して実行したところ、以下のような結果が得られました。
Metrics/AbcSize:
Enabled: true
Max: 27.0
Metrics/BlockLength:
Enabled: true
Max: 30
Metrics/ClassLength:
Enabled: true
Max: 69
Metrics/CyclomaticComplexity:
Enabled: true
Max: 5
Metrics/LineLength:
Enabled: true
Max: 99
Metrics/MethodLength:
Enabled: true
Max: 19
Metrics/ModuleLength:
Enabled: true
Max: 63
Metrics/PerceivedComplexity:
Enabled: true
Max: 5
Metrics/AbcSize
やMetrics/BlockLength
などはデフォルトの設定に比べてだいぶ緩められています。
反面、Metrics/ClassLength
などはプロジェクト内のクラスが比較的小さいのか、デフォルトの設定よりもかなりキツい値となっています。
既知の問題
現在 RuboCop の実装の都合上、Metrics/BlockNesting
, Metrics/ParameterLists
の2つの Cop に関しては必要な情報が取得できないため、自動で設定を生成することが出来ません。
RuboCop 本体を修正することで、将来的には設定を可能に出来るよう考えています。
まとめ
最新版の Gry では、上記のように Metrics Cop の設定を自動で生成出来るようになりました。
Metrics Cop のしきい値を程よく設定できるようにするため、是非ご活用下さい。