🕵️‍♂️

ぼっち演算子を無闇に使うとブランチカバレッジが悪化する

に公開

ブランチカバレッジが何かと言うと下記に記載があります。
https://github.com/simplecov-ruby/simplecov?tab=readme-ov-file#branch-coverage-ruby--25

↑では、三項演算子の例と後置ifの例が乗っています。

下記の例では、numberが奇数と偶数の両方の分岐を通すようなテストが書けていれば100%です。

number.odd? ? "odd" : "even"

本題のぼっち演算子の場合はこんな感じです。

sample.rb
def sample(a:)
  return if a.nil?

  p a&.to_s
end

sample(a: nil)
sample(a: 42)

上記の例だとぼっち演算子の手前で止まってnilが出力されるようなパターンのテストは書けそうもありません。

この時のブランチカバレッジがどうなるか実際にsimplecovを使って調べてみます。
simplecovを使うのに、わざわざrailsとrspecを準備する必要はなく下記のようなコードで調べられます。

require 'simplecov'
SimpleCov.start do
  enable_coverage :branch
end

require "#{__dir__}/sample.rb"

上記のコードをrubyで実行して、生成されたindex.htmlをワンラインサーバなどで見ると、下記の通りの結果になります。
※おすすめのワンラインサーバはserveです

lines coveredは100%ですが、branches coveredは75%になっていて、ぼっち演算子のところがちょうど赤くなっています。

また下記のようなパターンだとnilチェックすらブランチカバレッジが悪化する原因になります。

sample3.rb
class Sample
  def initialize(a:)
    raise ArgumentError, 'a cannot be nil' if a.nil?

    @a = a
  end

  def sample
    return if @a.nil?

    p @a.to_s
  end
end

Sample.new(a: 42).sample
Sample.new(a: nil).sample rescue nil

このパターンは割と実際のコードに近い気がします。インスタンス変数を使うと初期化しているコードとインスタンス変数を利用しているときのコードが離れたりしていて、とりあえずnilチェックしておこうとなりやすいと思います。

ただ、上記で挙げたパターンは、ブランチカバレッジが悪化したところで、実際のコードの品質には何も問題ないはずです。強いて、困り事をあげるとすれば、ciが落ちたりするくらいでしょうか。。。

今回使用したコードはこちらです

しくみのテックブログ

Discussion