ABC size を可視化し、闇を払う

これは Ruby Advent Calendar 2022 の7日目の記事です。

TL; DR

ABC size を可視化する abc_size_visualizer という gem を作りました。 こんな感じで、メソッドの各行がどれだけ ABC size の増加に貢献しているかを可視化する CLI ツールです。

赤が代入、黄色がメソッド呼び出し、青が条件式の数を表しています。

また、各行の末尾にある # <0, 2, 1> のようなコメントも abc_size_visualizer が付加したもので、それぞれ代入、メソッド呼び出し、条件式の大きさを表しています。

使い方

以下のコマンドでインストールできます。

$ gem install abc_size_visualizer

そして、以下のコマンドで ABC size の可視化を実行します

$ visualize_abc_size some_code.rb
...
# ABC size の情報が付与された some_code.rb の内容が出力される

背景

abc_size_visualizer を実装するに至った背景を説明します。

ABC size はどのように理解されているか

多くの Rubyist は、以下のような警告によって ABC size を知ります。

$ bundle exec rubocop
(略)
Metrics/AbcSize: Assignment Branch Condition size for auth is too high. [20.5/15]

このような警告に直面した人々は、インターネットを検索して回った後、おもむろに .rubocop.yml を編集し、ABC size に関する警告を「解決」します 1

Metrics/AbcSize:
 Max: 100

このように、一般的に ABC size は、

  • なんかよくわからないけど rubocop が怒ってくるもの
  • (ホントは良くないと分かっているが... という disclaimer 付きで)無視するもの

と理解されています。

ABC size とは何であるか

では、そもそも ABC size とは何でしょうか?

ABC size は、代入(assignment)、メソッド呼び出し(branch)、条件式(condition)の出現回数をそれぞれ2乗して和をとり、その平方根を計算したもの2であり、コードの「サイズ」を表す指標です3

定義を述べてもよくわからないと思うので、例を挙げます。以下のメソッドを考えると、

def some_method
  if [true, false].sample # if による条件式(C)、sample メソッドの呼び出し(B)
    puts "#{100 + 200}" # puts メソッドの呼び出し(B)、 `+` メソッドの呼び出し(B)
  end
end

ABC size は、

 \displaystyle
\sqrt{A^2 + B^2 + C^2} = \sqrt{0^2 + 3^2 + 1^2} \simeq 3.2

となります。

ABC size の問題点

ABC size の問題点は分かりづらいことにあります。

例えば、以下のコードには「メソッド呼び出し(branch)」が何回出現するでしょうか?

puts user_name

正解は..................「わからない」です!

なぜなら、 user_name がローカル変数なのかメソッド呼び出しなのか、この一行だけでは判定できないからです。

ABC size は、メソッド全体の文脈を加味しないと算出することができず、見た目が全く同じコード断片であっても、文脈によって ABC size は全く異なる可能性があります。

def some_method1
  puts user_name # user_name というローカル変数がないので user_name はメソッド呼び出しである
end

def some_method2(user_name:)
  puts user_name # user_name というローカル変数が定義されているので user_name はメソッド呼び出しではない
end

ほかにも、ABC size には非自明なケースがいくつも存在します。

  • メソッドの引数は assignment としてカウントされるか?
  • &.some_method は condition としてカウントされるか? branch としてカウントされるか?
  • rescue SomeError => e は condition としてカウントされるか? assignment としてカウントされるか?
  • else は condition としてカウントされるか?
  • ||= は condition としてカウントされるか? assignment としてカウントされるか?
  • > は condition としてカウントされるか? branch としてカウントされるか?
  • デフォルト値付きのキーワード引数は assignment としてカウントされるか? condition としてカウントされるか?

また、これらの非自明なケースはドキュメント等にも特に記載がありません。 そもそも、Rubocop の Metrics/AbcSize では ABC の内訳は表示されません。

そのため、ABC size 削減チャレンジは、

  1. 当てずっぽうで色々書き換える
  2. rubocop に怒られないことを祈る
  3. 怒られたら 1 に戻る

という方法で行われる4ことになり、不毛度が高いです。

abc_size_visualizer で ABC size の闇を払う

abc_size_visualizer の存在する世界では、ABC size 削減チャレンジは

  1. ABC size を可視化し、ABC size が大きい原因を理解する
  2. 問題となっている行を書き換える
  3. rubocop に怒られないことを確認する

というステップで実施することができます。

また、rubocop に鍛えられて Ruby のベストプラクティスを学ぶように、人間側が ABC size をより深く理解するようになり、abc_size_visualizer がなくても適切に ABC size を削減できるようになっていくことでしょう。

実装方法とその問題点

abc_size_visualizer は、rubocop の内部で利用されている AbcSizeCalculator クラスを流用して実装しています。

これは、rubocop の ABC size 計算と実装をなるべく一致させることを意図したものですが、今後 rubocop 側の実装が変更されたときに追従できない可能性が高いため、なんらかのうまいやり方を考える必要があるナアと思っています...

まとめ

ABC size を可視化する CLI ツールを実装しました。 これにより、rubocop の Metrics/AbcSize の闇を払い、Rubyist の皆さんをサポートすることができれば嬉しいです。

また、この CLI ツール自体の実装には闇が多いため、どうにかマトモにしていきたいなと考えています。


  1. # rubocop:disable Metrics/AbcSize によって「解決」することもある。
  2. https://nacl-ltd.github.io/2016/02/23/ruby-abcmetrics.html から。
  3. https://en.wikipedia.org/wiki/ABC_Software_Metric から。ソースコードの「複雑さ」や「計算量」を表す値ではないことに注意。
  4. ABC size を完全に理解している人は別ですが