rescue、ensureでの注意

あまり意識して使っていなかったのですが、意識せざるをえない状況になってしまったので、あとで「あれって何だっけ?」とならないためのメモを書いておきます。

例外処理を使うときの注意

こういうことはわりとよくやると思います。(こんな単純なことはさすがにしませんが)

def test(div)
  100 / div
rescue
  0
end

意図するところは「divがゼロじゃないときは 100を divで割った値が返ってきて、ゼロのときはゼロが返ってくる」というものです。これは何の問題もなく動きます。
では「divが 100未満だったら割った値を返して、それ以外はゼロを返す」というときはどうするでしょうか。100以上のときもゼロのときもゼロを返すんだから、こう書きたくならないでしょうか?

def test(div)
  return 100 / div if div < 100
rescue
ensure
  0
end

これで、test(200)とか test(0)とやると、nilが返ってきます。そういう風にしたければ、こうしないといけません。

def test(div)
  r = 0
  r = 100 / div if div < 100
rescue
ensure
  return r
end

理由

どうしてでしょう?

begin式全体の評価値は、本体/rescue節/else節のうち最後に評価された文の値です。また各節において文が存在しなかったときの値はnilです。いずれにしてもensure節の値は無視されます。

http://www.ruby-lang.org/ja/man/html/_C0A9B8E6B9BDC2A4.html#a.ce.e3.b3.b0.bd.e8.cd.fd

この通りで、ensure節の値は無視されるので、returnを使わずに書くとすればこうなります。

def test(div)
  return 100 / div if div < 100
  0
rescue
  0
end

みっともないですね。「ensure節の値が無視される」ことをわかった上で、return を使って書いたほうが自分好みです。
あと、勘違いしていましたが、ensure節に returnを書くと、本体で正常に returnするケースでも ensure節で returnしてしまいます。

def test(div)
  return 100 / div if div < 100
rescue
ensure
  return 0
end

こういう書き方をしたらダメということです。