Tech Racho エンジニアの「?」を「!」に。
  • 開発

Rubyの`and`/`or`演算子の良い使い道(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

Rubyのand/or演算子の良い使い道(翻訳)

どのチュートリアルや書籍にも、Rubyにはよく似た2セットの演算子「&&/||」と「and/or」があると書かれています(ビット操作用の&/|もありますが今回は扱いません)。しかしなぜ2セットもあるのかという理由について詳しく述べているチュートリアルはあまりありません。

一部のチュートリアルやブログ記事では「&&||ですべてのケースを十分カバーできる」「and/orは混乱を呼ぶだけなので決して使うべからず」と明確に述べているものすらあります。たいていの場合、and演算子の優先順位が=よりも低いという警告を見せつけられますが、演算子の優先順位が低いことの有益な利用法については誰も説明していません。

残念なことに、Rubyスタイルガイドでは(Rubocopが強制することもあって)これらの演算子の利用を明示的に禁止し、フロー制御にすら&&||の利用を勧めています。(ちなみにこれは私が自分のプロジェクトにRubocopを追加したら断固として即オフにする設定のひとつです)。

しかしandorはあなたの友人であり、コードをクリーンかつDRYかつ読みやすくするのに役立ちます。私たちはまさにRubyのそうした点を愛しているのです。

and/orはフロー制御の演算子です

例:

File.exists? or raise "設定ファイルがない!"
document.ready? and return

これはRubyがPerlの書き方から受け継いだあまたの良き遺産のひとつです。非常にそっけないながら実に読みやすく、意図をはっきりと伝えます。

「どちらも&&||で同じように書けるじゃないか」と反論されるかもしれません。あるいは興味本位で「後置のifunlessだってあるのに何でフロー制御にそんなものを使わないといけないんだ」。

そこで、いくつかコード例を見ていくことにしましょう。

事例1. タスク「メソッドから何らかのデータを受け取り、受け取りに成功したらそのまま処理し、受け取りに失敗したら即座にreturn(または失敗)する」。たとえば複雑なNokogiriドキュメントをパースするとします。

def extract_actors(movie_doc)
  list = movie_doc.at('ul#actorsList')
  # docにリストがなければreturnし、あればいろんな処理をすべき

ここでどんな選択肢が使えるのでしょうか?

# オプション1
list = movie_doc.at('ul#actorsList')
return unless list # 独立ステートメントが余分にある

# オプション2
if (list = movie_doc.at('ul#actorsList'))
  # ==と区別するために余分な () がある
end

# オプション3〜20
# ...お好きにどうぞ!

# フロー制御演算子を使う
list = movie_doc.at('ul#actorsList') or return

意図が最もはっきりわかるのはどれですか?私はダントツで最後の例を取ります。そしてこのorは、まさに優先順位があるため||に置き換えられないのです。

事例2. チェックのリスト「何か複雑な処理をする前に、引数(とおそらくステート)をチェックし、何か問題があれば失敗し、そうでない場合のみ処理を進める」。

私は次のように書くのが好みです。

File.exists?(config) or raise "設定ファイル #{config} が見つかりません"
x.is_a?(Numeric) && y.is_a?(Numeric) or
  raise "数値以外は受け取れません"
halted? || waits_for_halt? and raise "プロセスは既に停止しました"

# ...後は普通に処理を進める

if/unlessを変形した同じコードはそこまでクリアではありません。

raise "設定ファイル #{config} が見つかりません" unless File.exists?(config)
raise "数値以外は受け取れません" unless x.is_a?(Numeric) && y.is_a?(Numeric)
raise "プロセスは既に停止しました" if halted? || waits_for_halt?

フロー制御演算子を用いたコードは「重要な条件」 or raise ...何か...と読めます。raise 「長い重要な文字列」 unless .... 何とかかんとかと読めます。

繰り返しますが、演算子の優先順位が(他にかっこに伴う面倒も)ある以上、ここで&&||を使いたい人はいないでしょう。

私は次の(少々難解な)定義が好みです。

論理演算子(&&||)は値を扱い、フロー制御演算子(andor)は副作用を扱う。

追伸: もちろんこういうことを声高に主張しているブログ記事はこれが初めてではありません(これこれこれもそうです)。私はただ、もうひとつ明快な論点を指摘したかっただけなです。

関連記事

Rubyスタイルガイドを読む: 文法(3)演算子とif/unless

Rubyの否定演算子2つ重ね「!!」(double-bang)でtrue/falseを返す


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。