Ruby: 例外処理とrescue/ensureの挙動について
プログラムの挙動が、期待通りにいかないことはよくあることだと思います。
そんな時に臨時で処理を行うものとして、例外処理がRubyには存在します。
基本的には例外処理はあまり使わないように実装すべきだと考えています。
しかしながら、得てして既に実装済みのコードなどで例外処理は存在するので、使わざるを得ないケースが存在します。
今回は、Rubyの例外処理の挙動を備忘録としてまとめておきます。
・目次
検証を行うために、まずはじめに そして、下記のような 準備したクラスを動かしていきます。 まず、テストする対象は、 では、実際に動かしてみます。 想定通りの例外出力となりました。これは特に問題ないと思います。 また 実行結果は以下のようになります。 Integer#times なので、返り値が ensure 節が存在する時は begin 式を終了する直前に必ず ensure 節の本体を評価します。 begin式全体の評価値は、本体/rescue節/else節のうち 最後に評価された文の値です。また各節において文が存在しなかったときの値 はnilです。いずれにしてもensure節の値は無視されます。 これによると、 下記のように書き換えます。 実行結果では、 例外処理の挙動
Gemと検証用のクラスを準備する
pry
という Rubygemをインストールしておきます。❯ gem install pry
Successfully installed pry-0.12.2
Parsing documentation for pry-0.12.2
Done installing documentation for pry after 1 seconds
1 gem installed
ExceptionTest
クラスを準備しておきます。[2] pry(main)> show-method ExceptionTest
From: (pry) @ line 1:
Class name: ExceptionTest
Number of lines: 21
class ExceptionTest
def test_rescue!
raise_zero_division_error!
rescue => ex
puts ex.message
end
def test_ensure!
raise_zero_division_error!
rescue => ex
puts ex.message
ensure
puts 'ensure'
end
private
def raise_zero_division_error!
1 / 0 # raise ZeroDivisionError
end
def raise_runtime_error!
raise RuntimeError
end
end
begin - rescue - end
ExceptionTest#test_rescue!
の挙動です。ExceptionTest
クラスのインスタンスメソッドである test_rescue!
が呼ばれるとZeroDivisionError
が発生して、 返り値は nil
を返します。[3] pry(main)> et = ExceptionTest.new
=> #<ExceptionTest:0x00007ff842404588>
[4] pry(main)> et.test_rescue!
divided by 0
=> nil
[5] pry(main)>
rescue
で例外を捕捉して、例外毎に処理を分けることも可能です。ExceptionTest
を下記のように変更して実行してみます。[7] pry(main)> show-method ExceptionTest.new.test_rescue!
From: (pry) @ line 2:
Owner: ExceptionTest
Visibility: public
Number of lines: 12
def test_rescue!
2.times do |i|
begin
raise_zero_division_error! if i == 0
raise_runtime_error!
rescue StandardError => ex
puts "#{ex.message}, iteration: #{i}"
rescue RuntimeErro => ex
puts "#{ex.message}, iteration: #{i}"
end
end
end
nil
ではなくなっています。[8] pry(main)> et = ExceptionTest.new
=> #<ExceptionTest:0x00007fe7a94b6390>
[9] pry(main)> et.test_rescue!
divided by 0, iteration: 0
RuntimeError, iteration: 1
=> 2
begin - rescue - ensure - end
ensure
の挙動 は ドキュメントに下記のように記載してあります。ensure
を定義した場合には、例外処理の如何に関わらず必ず評価されるはずです。[10] pry(main)> et.test_ensure!
divided by 0
ensure
=> nil
rescue で捕捉した例外を ensureで扱えるか
ensure
を評価する時に rescue
で捕捉した exception
がどのように扱われるか確認します。[11] pry(main)> show-method et.test_ensure!
From: pry-redefined(0x3ff3d4c98f94#test_ensure!) @ line 1:
Owner: ExceptionTest
Visibility: public
Number of lines: 7
def test_ensure!
raise_zero_division_error!
rescue => ex
puts ex.message
ensure
puts "#{ex.message} in ensure"
end
rescue
で捕捉した exception
は ensure
でも同様の変数名で扱えるようです。[12] pry(main)> et.test_ensure!
divided by 0
divided by 0 in ensure
=> nil
おさらい
rescue
で例外を捕捉して、任意の処理を実行できるensure
節は、 rescue
の実行有無に関係なく必ず評価されるrescue
で捕捉した例外については、 ensure
で扱うことが可能rescue
で exception
を入れないケースについては変数を呼び出すと例外( NameError
)を引き起こすので注意する