【Ruby】&&と&の違いについて
はじめに
&&
と&
の違いを答えよ! と問われたら、あなたは答えられますか?
私はあまり理解できておらず、色々調べたり、先輩社員に質問しました。
勉強になったので、備忘も込めてまとめてみたいと思います。
特に、初学者の皆さんの参考になれば幸いです。
結論
先に結論を述べておきます。
Aという条件
とBという条件
をさらにAND比較したい場合…A && B
とA & B
の違いは、
です。
詳細
では、実例を交えて詳細を説明したいと思います。(モデル名などは全てフィクションです)
この問題に出会ったきっかけ
商品と在庫を管理する購入(Purchase)モデル
で、このようなメソッドを書きました。
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 & 3
例2: 2 & 3 # => 2
ビット表現:
2 : 0010
3 : 0011
----------
結果: 0010 (=> 2)
この場合、2
と 3
のビットのうち、下位の1ビット目のみが異なっており、上位のビットが同じです。そのため、&
の結果は 0010
、すなわち 2
になります。
まとめ
例:
# `&&` の場合
false && some_method # some_methodは評価されない
# `&` の場合
false & some_method # some_methodは評価される
さいごに
私のような初学者にとっては、きちんと意味を理解しながら実装することの大切さを実感して、いい勉強になりました。加えて、面倒くさがらずきちんとテストを書くことの大切さも身にしみました。
どなたかの参考になれば幸いです。
株式会社ラグザイア(luxiar.com)の技術広報ブログです。 ラグザイアはRuby on RailsとC#に特化した町田の受託開発企業です。フルリモートでの開発を積極的に推進しており、全国からの参加を可能にしています。柔軟な働き方で最新のソフトウェアソリューションを提供します。
Discussion