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を返す

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

BPSアドベントカレンダー

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ