🦇

Rubyのプロダクトコードの複雑度の変化を可視化する

2023/12/05に公開

これは Ruby Advent Calendar 2023 の記事です。

最近仕事でリファクタリングに力を入れていることもあり、リファクタリングによってプロダクトコードの各種メトリクスがどのように変化したかを知りたくなりました。

そこで git のコミットごとにプロダクトコードの複雑度・コードスメルがどのように変化したかを可視化するツールを作りました。

https://github.com/mizoR/zubat

使い方

使い方は引数に可視化したい Ruby のスクリプトのファイルパスを指定するだけです。

$ zubat [FILE]...

たとえば、Ruby の find ライブラリ と base64 ライブラリの複雑度の変化を可視化したい場合は以下のコマンドです。

$ cd path/to/ruby # ruby のプロジェクトルートに移動

$ zubat lib/find.rb lib/base64.rb

結果は HTML で生成されます。下のキャプチャのように複雑度の変化がグラフで可視化されます。

また、コードスメルの種類別の報告数の変化も可視化されます。

さらにグラフ上のポイントをクリックするとそのコミットで行った diff が表示できます。そのためどのようなコミットしたことでメトリクスにどのような変化が起こったのかをブラウザ上で確認できます。

仕組み

コードの変更履歴は git のコミットログに基づいています。コミットログをツールが読み込むために以下の外部コマンドを実行し、JSON形式でコミットログを標準出力へ流してツール内でパースしています。

$ git log --oneline --pretty=format:'{ "sha": "%h", "time": "%ad" }

あとはここで取得したコミットIDを使って当時のコードやコミットの内容を取得し、複雑度やコードスメルを計算させるだけです。

複雑度の計算には flog.gem を使っています。コードを文字列として入力に渡すことが難しそうでしたが ファイルとして渡すことができるようでしたので、Tempfile を使って一時的にファイルに書き込み Flog に複雑度の計算を任せました。

Tempfile.open do |file|
  file.print(code)
  file.close

  flog = ::Flog.new
  flog.flog(file.path)

  { total_score: flog.total_score, average: flog.average }
end

コードスメルには reek.gem を使っています。こちらは Reek::Examiner をコードの文字列で初期化することで計算させることができました。Reek::Examiner は引数となる文字列を破壊的に変更するらしく、ツール内では常に frozen_string_literal を true にしていたため明示的に凍結されていない文字列インスタンスに変換した上で引数として与える必要がありました。

smells = Reek::Examiner.new(String.new(code)).smells

まとめ

  • コミット単位で複雑度とコードスメルの変化をグラフとして可視化できるようにしました。
  • リファクタリングを含む様々な変更がプロダクトコードの複雑度やコードスメルにどのような変化を及ぼしたのかを後からで客観的に追跡できるようになりました。
  • 興味がありましたらぜひお試しください。

Discussion