こんにちは、hachi8833です。
「Rubyスタイルガイドを読む」シリーズ、文法編その3いってみましょう。
- Rubyスタイルガイドを読む: 総もくじ
- 前回: Rubyスタイルガイドを読む: 文法(2)アンダースコア、多重代入、三項演算子、if/unless
- 次回: Rubyスタイルガイドを読む: 文法(4)ループ
文法(3)演算子、if/unlessなど
2-15【統一】否定は!
演算子で表すこと(not
は使わない)
Use
!
instead ofnot
.
例からわかるようにnot
だと余分なかっこがいかにも目ざわりなので、それを避けるのが目的かなと思ったのですが、morimorihogeさんから「not
の結合優先順位が(and
やor
にも似て)かなり低いから想定外の挙動になる可能性があり、それを避けるためでしょう」という指摘をいただきました。
# 不可 - かっこが必要
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 anil
check, usenil?
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
inc
とdec
を実行してもレジスタAX
の値は変わりませんが、レジスタの値がもともとゼロであればCPUのゼロフラグだけが設定されるという性質を利用しています。inc
やdec
はレジスタの値を1増やす/減らす命令ですが、ADD AX, 1
よりずっと動作が早いそうです。
これならcmp <op1> <op2>
のような重たい比較命令も使わずに済みますし、特にC言語ではゼロかどうかで条件分岐をすることが非常に多いので、コンパイラの最適化として効果が高いはずです。
このトンチに当時感心したものでした。
2-17【統一】論理演算には常に演算子&&
と||
を使う(and
とor
は使わない)
The
and
andor
keywords are banned. It's just not worth it. Always use&&
and||
instead.
キーワードand
とor
を使っても何もいいことがないとまで言い切ってます。
# 不可
# 論理式に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!
キーワードand
とor
はそもそも演算子&&
や||
より優先順位が低く、キーワードと演算子を混用すると予期せぬ動作になりやすいからだと推測しました。
さらに英語のandやorと紛らわしくなります。今さらですが、英語のandやorは論理演算のandやorとかなり違います(ずっと高機能です)。
追伸: あえてand
を使う場合
これまたmorimorihogeさんから、RailsのControllerでは以下のような書き方をすることがあると追伸いただきました。
redirect_to :hoge and return
確かにこれならif
とthen
で書くのと異なり1行で書けますし、このようなand
ならむしろわかりやすく、結合優先順位上でも明確になりますね。
2-18【統一】三項演算子?:
は複数行では使わない
Avoid multi-line
?:
(the ternary operator); useif
/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
overif
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
2-23【統一】unless
とelse
を併用しないこと
Do not use
unless
withelse
. Rewrite these with the positive case first.
unless
でelse
が使われていると、脳に負担がかかるのがありありとわかります。納得です。
# 不可
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
などのループ関連です。
関連記事
- Rubyスタイルガイドを読む: ソースコードレイアウト(1)エンコード、クラス定義、スペース
- Rubyスタイルガイドを読む: ソースコードレイアウト(2)インデント、記号
- Rubyスタイルガイドを読む: 文法(1)メソッド定義、引数、多重代入
- Rubyスタイルガイドを読む: 文法(2)アンダースコア、多重代入、三項演算子、if/unless
- Rubyスタイルガイドを読む: 文法(3)演算子とif/unless
- Rubyスタイルガイドを読む: 文法(4)ループ
- Rubyスタイルガイドを読む: 文法(5)ブロック、proc
- Rubyスタイルガイドを読む: 文法(6)演算子など
- Rubyスタイルガイドを読む: 文法(7)lambda、標準入出力など
- Rubyスタイルガイドを読む: 文法(8)配列や論理値など
- Rubyスタイルガイドを読む: 命名
- Rubyスタイルガイドを読む: コメント、アノテーション、マジックコメント
- Rubyスタイルガイドを読む: クラスとモジュール(1)構造
- Rubyスタイルガイドを読む: クラスとモジュール(2)クラス設計・アクセサ・ダックタイピングなど
- Rubyスタイルガイドを読む: クラスとモジュール(3)クラスメソッド、スコープ、エイリアスなど
- Rubyスタイルガイドを読む: 例外処理
- Rubyスタイルガイドを読む: コレクション(Array、Hash、Setなど)
- Rubyスタイルガイドを読む: 数値、文字列、日時(日付・時刻・時間)
- Rubyスタイルガイドを読む: 正規表現、%リテラル、メタプログラミング(最終回)