何でも屋エンジニアのブログ

ソフトウェア関連技術、コミュニティ、日々の雑貨

コードの品質に関するメトリクスを取得するGemをリリースした

CodeKeeperというGemをリリースした。循環的複雑度、ABCソフトウェアメトリクス、クラスの行数という品質面にまつわるメトリクスを取得するGemで、Rubyファイルを対象にしている。

github.com

動機

主に以下の3つである。

  • Four keysのような生産性を測る指標とは別に、内部的な品質に関する指標を取りたかった
    • 継続的な改善を続けた結果としての変化を見たかった
  • コードを解析するコードを書いてみたかった
    • Saasなどもあるが自分で書いてみたかった
  • Gemを1から書いて公開したことがなかったのでやりたかった

使い方

メトリクス・出力形式

対応しているメトリクスは

  • 循環的複雑度(ファイル)
  • ABCソフトウェアメトリクス(ファイル)
  • クラスの行数

である。

前者2つは、実装の簡便さを鑑みファイル単位とした。また、出力について、取得したメトリクスをBigQueryなどに取り込めるように、csvもしくはjsonにて標準出力に吐き出す形とした。

設定

以下のような設定で、

  • 取得するメトリクス
  • 実行スレッド数
  • 出力フォーマット

を選択できる。

CodeKeeper.configure do |config|
  # If you choose metrics, specify as follows:
  config.metrics = %i(cyclomatic_complexity abc_metric class_length)
  # The number of threads. The default is 2. Executed sequentially if you set 1.
  config.number_of_threads = 4
  # The default is json
  config.format = :csv
end

以下は例としてGitlabの適当なファイルに実行した結果である。

$ bundle exec code_keeper app/models/user.rb app/models/ability.rb > metrics.csv 
$ cat metrics.csv                
metric,file,score
cyclomatic_complexity,app/models/ability.rb,9
cyclomatic_complexity,app/models/user.rb,24
class_length,Ability,86
class_length,User,1494
abc_metric,app/models/ability.rb,76.909
abc_metric,app/models/user.rb,1549.264
$

調べたこと

CodeKeeperを作るにあたり、以下をキャッチアップした。 コード解析やASTについてはRubocopのソースやRubocopAstのドキュメントを読み、実際に手を動かし挙動を確認した。その際pockeさんの記事にはとても助けられました。ありがとうございます。 qiita.com

pocke.hatenablog.com

Class length metricを作るときに参考にした rubocop/class_length.rb at master · rubocop/rubocop · GitHub 周りのコードが特に勉強になった。一行一行動かしながら意味を理解するのも楽しかった。return unless block_node.respond_to?(:class_definition?) && block_node.class_definition? の意味がわからなかったが以下のissueを見てなるほどーとなった。 github.com

実装の多くはRubocopを参考にしており、本当に感謝しかない。

メトリクスについて、株式会社Sider様のブログがとても参考になった(ありがとうございます)。基本的にはその記事のリンクを辿ったり、GMetricsの対応しているメトリックを調べたりして勉強した。

siderlabs.com siderlabs.com

dx42.github.io

作ってみて

Rubocopに依存しすぎている(private apiも使っている)のではがして実装しようと思っていたが、Rubocopがとてもよくできているし、中身読んで理解するだけで勉強になるしこれで良いかなという気持ちや、細かいところしっかり考えるのかなり大変そうという理由もあり、現在はRubocopに依存しているMetricsが多い。とはいえ、Rubocopでは内部的にMetricを計算した後違反の有無に応じて結果を保存しているため、結果(スコア)を取得することができずカスタマイズする必要はあったが。

また、アプデに追従するの大変かもしれないとも考えたが、依存先がそこまで多くないし問題にはならないだろう。そして作って感じたことだが、Rubyの表現力が高くバグにつながるパターンは(自分のRuby力では)ある程度ユーザーに見つけてもらう必要があり、そう考えると既に利用者が多くFBに対しきちんと対応しているRubocopに依存するのも悪くない選択肢だろうと考えた。

その他

Rubyでどう書くか、そしてそれがどう構文解析されるかに関する理解が深まるので、静的解析に関して遊ぶのは結構有益だなと感じた。

余談だが、README.mdにも書いた通りメトリクスを取得することはコードをきれいにすることに繋がるのでCodeKeeperという名前をつけたが、キレイにするのは人なので単純にRMetricsとかにすれば良かったかなと思っている。