例外が発生した際に、rescue
句 や ensure
句で新しい例外を発生させる場合があります。
begin
raise "Error A"
rescue
raise "Error B"
end
呼び出し元によりわかりやすいエラーメッセージを伝えるために、
独自の例外を作って投げ直したりする場合です。
具体的には以下の様なコードです。
class OreOreError < StandardError; end
def divide_by_zero(num)
num / 0
end
def calculate(num)
divide_by_zero(num) # raise ZeroDivisionError
rescue => e
raise OreOreError.new("#{e.class} が「#{e.message}」って言ってるわ!")
end
begin
calculate(100)
rescue => e
p e #=> #<OreOreError: ZeroDivisionError が「divided by 0」って言ってるわ!>
end
ただこの場合、問題があります。
以前に発生した例外 (上記のコードの場合は ZeroDivisionError) の情報が消えてしまうということです。
例えば、もとの例外の Exception#backtrace
が分からなくなるため、
デバッグに支障をきたすこともあるでしょう。
しかし、ここで朗報です!
Ruby 2.1 で追加された Exception#cause
を使えば、以前に発生した例外もたどれるようになりました。
begin
calculate(100)
rescue => e
puts "現在のエラー: #{e.inspect}"
puts "1つ前のエラー: #{e.cause.inspect}"
end
# 現在のエラー: #<OreOreError: ZeroDivisionError が「divided by 0」って言ってるわ!>
# 1つ前のエラー: #<ZeroDivisionError: divided by 0>
全ての例外をたどって backtrace を出せるようにしてみたり。
class Exception
def full_backtrace
return [self.inspect] + backtrace unless cause
[self.inspect] + backtrace + cause.full_backtrace
end
end
puts e.full_backtrace * "\n"
# #<OreOreError: ZeroDivisionError が「divided by 0」って言ってるわ!>
# oreore.rb:22:in `rescue in calculate'
# oreore:19:in `calculate'
# oreore:26:in `<main>'
# #<ZeroDivisionError: divided by 0>
# oreore.rb:15:in `/'
# oreore.rb:15:in `divide_by_zero'
# oreore.rb:19:in `calculate'
# oreore.rb:26:in `<main>'
Ruby 2.0 以前の場合
Ruby 2.0 以前の場合は Exception#cause
が使えないため、少し工夫が必要です。
例外オブジェクトのインスタンス変数に、以前の例外を持たせるようにします。
$!
は Ruby の組み込み変数 (Kernel の特殊変数) の1つで、
最後に例外が発生したときの Exception オブジェクトを持っています。
class OreOreError < StandardError
attr_reader :previous # Exception#cause の代わり
def initialize(msg)
super(msg)
@previous = $!
end
end
def divide_by_zero(num)
num / 0
end
def calculate(num)
divide_by_zero(num) # raise ZeroDivisionError
rescue => e
raise OreOreError.new("#{e.class} が「#{e.message}」って言ってるわ!")
end
begin
calculate(100)
rescue => e
puts "現在のエラー: #{e.inspect}"
puts "1つ前のエラー: #{e.previous.inspect}"
end
# 現在のエラー: #<OreOreError: ZeroDivisionError が「divided by 0」って言ってるわ!>
# 1つ前のエラー: #<ZeroDivisionError: divided by 0>