unlessはいつ、どう使う?
rubyには条件分岐のために if
式があります。(文ではない)
if
式は条件が真である場合をひっかけるときに使用するのは皆さんご存知の通りです。
if (true_condition) {
...
}
if
式に対して unless
式も存在しており、条件が偽である場合をひっかけます。
unless (false_condition) {
...
}
なので
if (condition) {
...
}
と
unless (!condition) {
...
}
は等価であり相互に書き換え可能です。
さて、この if
式と unless
式はどう使い分けをすれば良いでしょうか。
そのヒントがC言語の assert
マクロにあります。
C言語の assert マクロ
C言語には ANSI 標準の assert
マクロがあって、式が 0
(偽)であった場合に処理をそこで終了する機能を持っています。
int main(void)
{
int a = 2, b = 5, c;
c = a + b;
assert(c == 7); // c が 7 ではなかったら終了
}
要は「絶対真になるはず」という条件を指定しておいて、万が一そうで無い状況が発生した場合に(異常終了という形で)検知できるようにするためのデバッグ用マクロです。
単純な仕組みながら、こんなはずではなかった。というバグを捉えるのに非常に有用なマクロです。
unless は assert の代わりとして使うべし
ruby に assert
はありませんが、 unless
をその代わりと思うとその使い道がはっきりします。
先程の例をrubyのコードに書き換えてみましょう。
def main
a = 2, b = 5
c = a + b
raise AssertError unless(c == 7) # c が 7 ではなかったら終了
end
unless
で真であるはずの c == 7
という条件が満たされなかった場合に例外を raise します。
if
にすると、条件式が反転しちゃいますね。条件式に否定が入るのは混乱の元なので避けたいところです。
raise AssertError if(c != 7) # c が 7 ではなかったら終了
unless
は assert
の代わりと考えるとこちらのほうがわかりやすいですよね。
実際の unless のわかりやすい使い方
メソッドやループの最初で(真であるべき)前提条件をチェックする(いわゆるガード節)ために使いましょう。
非常に恣意的な例ですが、メソッドの引数が偶数であることを期待している場合はこんな風に書けます。
def even_only(n)
return unless n.even? # 偶数じゃなかったら終了
...
end
ループの場合で、要素が偶数じゃなかった場合はスキップするという場合はこんな風に書けます。
def process_even_only(ns)
ns.each do |n|
next unless n.even? # 偶数じゃなかったらスキップ
...
end
end
(この場合は ns.reject(&:even?).each
という書き方もありですが )
もし、これを if
で書くと
return if !n.even? # または n.odd? だけど、どちらもわかりずらい。
となってしまいます。わかりずらいですね。
上手に unless を導入しよう
全ての道具にはそれぞれ適切な使い方があります。
せっかくあるものは有効に活用していきたいものです。
もしチームの文化的に unless
に慣れていなくて、いきなり使うのを躊躇するのであれば、意図をコメントとして追加しておくと良いでしょう。