🐕

【Ruby】&&と&の違いについて

2024/10/16に公開

はじめに

&&&の違いを答えよ! と問われたら、あなたは答えられますか?
私はあまり理解できておらず、色々調べたり、先輩社員に質問しました。
勉強になったので、備忘も込めてまとめてみたいと思います。
特に、初学者の皆さんの参考になれば幸いです。

結論

先に結論を述べておきます。
Aという条件Bという条件をさらにAND比較したい場合…A && BA & Bの違いは、

です。

詳細

では、実例を交えて詳細を説明したいと思います。(モデル名などは全てフィクションです)

この問題に出会ったきっかけ

商品と在庫を管理する購入(Purchase)モデルで、このようなメソッドを書きました。

app/models/purchase.rb
class Purchase < ApplicationRecord
  def available?
    unless product.released? && stock.available?
      stock.errors.each do |attribute, message|
        product.errors.add(attribute, message)
      end
      return false
    end

    true
  end
end

このメソッドは、「商品(product)が発売日以降か」、「在庫(stock)があるか」をチェックし、問題があった場合は、在庫のエラーも含めた全てのエラーメッセージをpurchase.product.errorsに記録するというものです。(エラーメッセージ等の定義は省略します)

テストに通らずバグ発見

抜けがあるかもしれないとレビューを受け、テストを追加して実行すると…NGになりました😨

「商品が発売日前(false)」かつ「在庫がない(false)」場合、両方のエラーメッセージが返ってくるかどうかを検証するテストです。

RSpec.describe Purchase, type: :model do
  describe '#available?' do
    it '商品が発売日前かつ在庫がない場合、falseとerrorsが返ること' do
      product = Product.new(released: false)  # 商品がまだ発売されていない
      stock = Stock.new(available: false)     # 在庫がない
      purchase = Purchase.new(product: product, stock: stock)

      expect(purchase.available?).to eq false
      expect(purchase.product.errors.full_messages)
        .to eq(['この商品は発売日前です', '在庫がありません'])
    end
  end
end

テスト結果は、以下の通り。
期待したエラーメッセージは『発売日前です』と『在庫がありません』の両方でしたが、実際には『発売日前です』だけしか返されず、在庫有無の評価が実行されていませんでした。

Failures:

  1) Purchase#available? 商品が発売日前かつ在庫がない場合、falseとerrorsが返ること
     Failure/Error: expect(purchase.product.errors.full_messages).to eq(['発売日前です', '在庫がありません'])
     
       expected: ["この商品は発売日前です", "在庫がありません"]
            got: ["この商品は発売日前です"]

ちなみに、product.released?だけ、stock.available?だけがfalseの場合のテストはちゃんとpassしています。。

ここで結論にもどります

期待通りの結果が得られなかった原因は、

だからです。

  unless product.released? && stock.available?

この書き方だと、
左辺が偽(product.released? == false)→ 評価終了
となり、右辺のstock.available?を評価してくれないのでerrorsも記録されないのです。。

これは短絡評価といわれるもので、左辺が偽の場合、論理ANDの結果はfalseに確定するので、右辺の評価をスキップすることで、不要な評価をするコストを減らそうという考え方です。

Rubyリファレンスマニュアル:演算子式>"and(&&)"


これです。今回は、こっちが正解だったのです!
恥ずかしながら、ここで初めて&を知りました。

& は、下のイメージのように、左辺のオブジェクトに対するメソッド呼び出しとして機能します。

(メソッドの対象となるオブジェクト)  &  (メソッドの引数となるオブジェクト)

こう考えると、左辺がfalseでも、(引数として機能させるために)右辺を必ず評価するイメージが付くのではないでしょうか。

このことから、以下のように書くと、product.released?false でも、stock.available? が必ず評価されます。

  unless product.released? & stock.available?

これで、期待通り全てのエラー['この商品は発売日前です', '在庫がありません']の結果を得ることが出来ました。

Rubyリファレンスマニュアル:
ライブラリ一覧>組み込みライブラリ>TrueClassクラス>"&"
ライブラリ一覧>組み込みライブラリ>FalseClassクラス>"&"
ライブラリ一覧>組み込みライブラリ>Integerクラス>"&"

※今回は触れませんでしたが、Integerクラスではビット演算の論理積を返します

Integerクラスにおける&の役割について

ビット演算とは、2進数の値をビットごとに比較し、両方のビットが1である場合にそのビットを1とします。以下は、数値のビット単位での動作の具体例です。

例1: 1 & 1

1 & 1  # => 1

ビット表現:

1  : 0001
1  : 0001
----------
結果: 0001 (=> 1)

この場合、両方の数のビットがすべて一致しているため、結果は 1 になります。

例2: 2 & 3

2 & 3  # => 2

ビット表現:

2  : 0010
3  : 0011
----------
結果: 0010 (=> 2)

この場合、23 のビットのうち、下位の1ビット目のみが異なっており、上位のビットが同じです。そのため、& の結果は 0010、すなわち 2 になります。

まとめ

例:

# `&&` の場合
false && some_method # some_methodは評価されない

# `&` の場合
false & some_method  # some_methodは評価される

さいごに

私のような初学者にとっては、きちんと意味を理解しながら実装することの大切さを実感して、いい勉強になりました。加えて、面倒くさがらずきちんとテストを書くことの大切さも身にしみました。
どなたかの参考になれば幸いです。

参考記事:【Ruby演算子】「&&、&、and」や「|| 、| 、or」 の違い

ラグザイア

Discussion