mai7star’s diary

練習用ブログなんで何でも無節操に書きます。

Rubyの unless は assert の代わりと思うとわかりやすい

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 の代わりとして使うべし

rubyassert はありませんが、 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 ではなかったら終了

unlessassert の代わりと考えるとこちらのほうがわかりやすいですよね。

実際の 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 に慣れていなくて、いきなり使うのを躊躇するのであれば、意図をコメントとして追加しておくと良いでしょう。