Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

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

こんにちは、hachi8833です。

「Rubyスタイルガイドを読む」シリーズ、文法編その3いってみましょう。

文法(3)演算子、if/unlessなど

2-15【統一】否定は!演算子で表すこと(notは使わない)

Use ! instead of not.

例からわかるようにnotだと余分なかっこがいかにも目ざわりなので、それを避けるのが目的かなと思ったのですが、morimorihogeさんから「notの結合優先順位が(andorにも似て)かなり低いから想定外の挙動になる可能性があり、それを避けるためでしょう」という指摘をいただきました。

# 不可 - かっこが必要
x = (not something)

# 良好
x = !something

2-16【統一】!!は避ける

!! converts a value to boolean, but you don't need this explicit conversion in the condition of a control expression; using it only obscures your intention. If you want to do a nil check, use nil? instead.

!!は「double-bang」とも呼ばれ、nilを含む戻り値をfalse/trueにキャストするのによく使われますが、このスタイルガイドではnilかどうかをチェックする場合には利用を勧めていません。

# 不可
x = 'test'
# nilチェックであることがわかりにくい
if !!x
  # (略)
end

# 良好
x = 'test'
unless x.nil?
  # (略)
end

以下の参考記事で紹介したようなtrue/falseへのキャストとしての!!はRailsのあちこちで使われていますが、nilチェックはキャストとは意味が異なるのでnilチェックには使うものではないということですね。

余談

本題から離れますが、!!を知ったとき、x86系アセンブラのコンパイラ最適化で以下のようなゼロ値チェックがあったのを連想しました。レジスタAXがゼロかどうかをたった2命令でチェックしています。

inc AX
dec AX

incdecを実行してもレジスタAXの値は変わりませんが、レジスタの値がもともとゼロであればCPUのゼロフラグだけが設定されるという性質を利用しています。incdecはレジスタの値を1増やす/減らす命令ですが、ADD AX, 1よりずっと動作が早いそうです。

これならcmp <op1> <op2>のような重たい比較命令も使わずに済みますし、特にC言語ではゼロかどうかで条件分岐をすることが非常に多いので、コンパイラの最適化として効果が高いはずです。

このトンチに当時感心したものでした。

2-17【統一】論理演算には常に演算子&&||を使う(andorは使わない)

The and and or keywords are banned. It's just not worth it. Always use && and || instead.

キーワードandorを使っても何もいいことがないとまで言い切ってます。

# 不可
# 論理式にandが使われている
if some_condition and some_other_condition
  do_something
end

# 制御フローにorが使われている
document.saved? or document.save!

# 良好
# 論理式で&&を使う例
if some_condition && some_other_condition
  do_something
end

# 制御フローで||を使う例
document.saved? || document.save!

キーワードandorはそもそも演算子&&||より優先順位が低く、キーワードと演算子を混用すると予期せぬ動作になりやすいからだと推測しました。

さらに英語のandやorと紛らわしくなります。今さらですが、英語のandやorは論理演算のandやorとかなり違います(ずっと高機能です)。

追伸: あえてandを使う場合

これまたmorimorihogeさんから、RailsのControllerでは以下のような書き方をすることがあると追伸いただきました。

redirect_to :hoge and return

確かにこれならifthenで書くのと異なり1行で書けますし、このようなandならむしろわかりやすく、結合優先順位上でも明確になりますね。

2-18【統一】三項演算子?:は複数行では使わない

Avoid multi-line ?: (the ternary operator); use if/unless instead.

三項演算子は前回の記事にまとまっていたので、これもそちらに移動したいところですね。

2-19【統一】実行行が1つの場合は後置のif/unlessを推奨する(&&||による制御フローでもよい)

Favor modifier if/unless usage when you have a single-line body. Another good alternative is the usage of control flow &&/||.

後置、つまり文の後ろから条件を修飾するmodifierとしてのif/unlessは、1行できれいに収まるので私も大好きです。個人的には三項演算子よりずっと読みやすいと思っています。

# 不可
if some_condition
  do_something
end

# 良好
do_something if some_condition

# これも良好
some_condition && do_something

2-20【統一】複雑な複数行ブロックでの後置if/unlessは避ける

Avoid modifier if/unless usage at the end of a non-trivial multi-line block.

これも納得です。ブロックの最後に条件が突然追加されていると上から読み下せなくてつらいです。

# 不可
10.times do
  # (複数行の処理)
end if some_condition

# 良好
if some_condition
  10.times do
    # (複数行の処理)
  end
end

2-21【統一】後置のif/unless/while/untilはネストしないこと(必要なら&&/||で)

Avoid nested modifier if/unless/while/until usage. Favor &&/|| if appropriate.

以下のサンプルを見れば後置のネストがまずい理由はすぐおわかりいただけると思います。どちらのifが先に発動するのかいちいち立ち止まって考えたくありません。

# 不可
do_something if other_condition if some_condition

# 良好
do_something if some_condition && other_condition

2-22【統一】否定条件はなるべくunlessや制御フロー||を使って読みやすくする

Favor unless over if for negative conditions (or control flow ||).

if !if notよりunlessの方が簡潔なので、これは納得です。

# 不可
do_something if !some_condition

# 不可
do_something if not some_condition

# 良好
do_something unless some_condition

# これも良好
some_condition || do_something

Rubyにおけるunlessとコードの読みやすさについて

2-23【統一】unlesselseを併用しないこと

Do not use unless with else. Rewrite these with the positive case first.

unlesselseが使われていると、脳に負担がかかるのがありありとわかります。納得です。

# 不可
unless success?
  puts 'failure'
else
  puts 'success'
end

# 良好
if success?
  puts 'success'
else
  puts 'failure'
end

余談: 自然言語の二重否定

なお自然言語では、たとえば「〜しないわけではない」のような二重否定表現がしばしば登場しますが、こうした表現はイディオムとして確立しているので、二重否定であっても特に読みにくいとされたりはしません。

さらに「〜しないわけではない」は実際には部分否定を含んでいるので、二重否定を圧縮して打ち消すと意味が変わってしまいます。

  • 〜しないわけではない: 「常に〜するとは限らない」「〜しないこともある」(部分否定を含む二重否定) => 圧縮できない
  • 「〜しない」ではない: 「〜する」と同等(純粋な二重否定) => 二重否定を打ち消して圧縮できる

2-24【統一】制御式の条件はかっこ( )で囲まない

Don't use parentheses around the condition of a control expression.

条件のかっこが必須の言語もありますが、Rubyでは原則として条件をかっこで囲まないのがスタイルです。かっこ自体はあってもなくても変わりません。

# 不可
if (x > 10)
  # body omitted
end

# 良好
if x > 10
  # body omitted
end

Note that there is an exception to this rule, namely safe assignment in condition.

ご想像のとおりこのスタイルにもやはり例外があり、条件をかっこで囲まないと期待どおりに動作しないことがあります。これについては次回扱います。

今回はここまでとします。次回の文法編はwhileなどのループ関連です。

関連記事



CONTACT

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