の例外処理(begin - rescueとraise)をもう一度きちんと確認してみた。


例外処理について調べていてこの記事(begin~rescue~ensureとraiseを利用した例外処理の流れと捕捉について)を見たけど、やっていることがよくわからなかったからきちんと調べてみた。

class ExceptionTest
  def test
    begin
      # 0での除算でエラーを発生させる
      1/0
    rescue ZeroDivisionError => ex
      puts "ZeroDivisionError"
    end
  end
end

obj = ExceptionTest.new
# 例外発生
obj.test # => ZeroDivisionError

なんの検証しているのだろう…。
ということで1からやりなおす。

main.rb
begin
  1 / 0
rescue
  puts "何か問題が発生しました。"
end

これはすごくわかりやすいし、よく使う。beginとrescueの間の処理で問題が起きたら、エラーでストップせずにrescueとend内の処理を行ってくれる。

main.rb
begin
  1 / 0
rescue
  puts "何か問題が発生しました。"
end
puts 'ok'

#=> 何か問題が発生しました。
#=> ok

これでもちゃんとストップせずに最後まで処理してくれる。

beginとrescueを使わないとエラーで止まってしまう。

main.rb
1 / 0
puts 'ok'

#=> main.rb:1:in `/': divided by 0 (ZeroDivisionError)
#=>     from main.rb:1:in `<main>'

1行目で0で割れないよーってエラーがでるから'ok'は表示されない。
次によくあるこれ。

main.rb
begin
  1 / 0
rescue => e
  puts "何か問題が発生しました。"
  p e
end
puts 'ok'

#=> 何か問題が発生しました。
#=> #<ZeroDivisionError: divided by 0>
#=> ok

これはエラーの内容をeに入れてくれる。begin ~ rescue間の処理でZeroDivisionErrorが発生したらしい。class ZeroDivisionError

で、この部分について楽しいRubyを見てみると、「例外オブジェクトを変数に代入」と書いてある。そして例外オブジェクトはメソッドを持っているらしい。

main.rb
begin
  1 / 0
rescue => e
  p e.class
  p e.message
  p e.backtrace
end
#=> ZeroDivisionError
#=> "divided by 0"
#=> ["main.rb:2:in `/'", "main.rb:2:in `<main>'"]

例外オブジェクトは例外クラスのインスタンスオブジェクトということなのかな?
リファレンスを見てみると例外クラスがたくさんある。


組み込みライブラリ
※まだまだ続く

つぎに参考サイトのこの部分を調べてみる。

rescue ZeroDivisionError => ex

rescueのあとにクラスを指定すれば、その例外のみrescueしてくれるらしい。

main.rb
begin
  1 / 0
rescue ZeroDivisionError
  puts "対象の例外やー。"
end

#=> 対象の例外やー。

0で割るとでるエラーはZeroDivisionErrorだから、これはきちんと動く。
でもこれは引数が足りないエラーだからArgumentErrorを指定してあげないとダメ。

main.rb
def full_name(firstname, lastname)
  puts firstname + lastname
end

begin
  full_name("taro")
rescue ZeroDivisionError
  puts "対象の例外やー。"
end

#=> main.rb:1:in `full_name': wrong number of arguments (1 for 2) (ArgumentError)
#=>     from main.rb:6:in `<main>'
main.rb
def full_name(firstname, lastname)
  puts firstname + lastname
end

begin
  full_name("taro")
rescue ArgumentError
  puts "対象の例外やー。"
end

#=> 対象の例外やー。

この辺まできて元ページのこの部分が何を検証しているのかわかってきた。

class ExceptionTest
  def test
    begin
      # 0での除算でエラーを発生させる
      1/0
    rescue StandardError => ex
      puts "StandardError"
    rescue ZeroDivisionError => ex
      puts "ZeroDivisionError"
    end
  end
end

obj = ExceptionTest.new
# 例外発生
obj.test # => StandardError

StandardErrorはZeroDivisionErrorの親クラスなので、StandardErrorもZeroDivisionErrorも対象の例外として0での除算エラーを捕捉できる。こういう場合どうなるのかを検証してたのかー。

次によく見かけるけどきちんと理解していなかったraiseを調べてみる。

例外を発生させます。第一の形式では直前の例外を再発生させます。 第二の形式では、引数が文字列であった場合、その文字列をメッセー ジとする RuntimeError 例外を発生させます。

raiseは例外を発生してくれるらしい。特に指定しなければRuntimeErrorを発生させるとのこと。

main.rb
raise "RunTimeError??"

#=> main.rb:1:in `<main>': RunTimeError?? (RuntimeError)
main.rb
raise ArgumentError

#=> main.rb:1:in `<main>': ArgumentError (ArgumentError)
main.rb
raise ArgumentError, "ArgumentErrorだよー"

#=> main.rb:1:in `<main>': ArgumentErrorだよー (ArgumentError)

こんな感じで指定してあげると例外を自由自在に操れるー。
ちなみにRunTimeErrorは

特定の例外クラスには該当しないエラーが起こったときに発生します。 また Kernel.#raise で例外クラスを指定しなかった場合も RuntimeError が発生します。

とのこと。ここまできて思い出したけど、raiseはif文とつなげて、

main.rb
def kufuku_gauge(energy)
   true if energy > 100
end

def eating_dinner
  raise "既に満腹!" if kufuku_gauge(120)
  puts "食事の時間だ!"
end

eating_dinner

#=> main.rb:6:in `eating_dinner': 既に満腹! (RuntimeError)
#=> from main.rb:10:in `<main>'

こんな感じでエラーメッセージを出してた。エラーを特定したい時に便利だ!
この辺までで元記事の意味は理解できるようになったー。