LoginSignup
30
30

More than 5 years have passed since last update.

例外をチェインした際に以前の例外を取得する

Last updated at Posted at 2014-05-20

例外が発生した際に、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>

参考

30
30
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
30