Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เชี่ยวชาญ Nested Exceptions ใน Ruby: ใช้ Exception#cause จัดการ Error อย่างแม่นยำ

Pattern ที่พบบ่อยใน Ruby คือ rescue exception แล้ว re-raise exception อีกชนิดหนึ่ง ActionView ตัวอย่างชัดเจน: โพสต์ก่อนเกี่ยว TracePoint กล่าวว่า ActionView นำ exceptions ใน templates ไป re-raise เป็น ActionView::TemplateError

บางครั้งไม่พอ คุณต้องการ original exception จริงๆ เพราะมี data ช่วยแก้ปัญหา โชคดีที่ Ruby 2.1+ มี Exception#cause ให้ใช้

มาดูการทำงานจริง: raise NoMethodError แล้วกลืนและ raise RuntimeError จับ RuntimeError ใช้ #cause ดึง NoMethodError ต้นทาง:


def fail_and_reraise
  raise NoMethodError
rescue
  raise RuntimeError
end

begin
  fail_and_reraise
rescue => e
  puts "#{ e } caused by #{ e.cause }"
end

Nested backtraces และ custom attributes

#cause คืน original exception object จริงๆ หมายความว่าเข้าถึง metadata ทั้งหมดของ original exception ได้ รวมถึง backtrace ต้นทาง


class EatingError < StandardError
  attr_reader :food
  def initialize(food)
    @food = food
  end
end

def fail_and_reraise
  raise EatingError.new("soup")
rescue
  raise RuntimeError
end

begin
  fail_and_reraise
rescue => e
  puts "#{ e } caused by #{ e.cause } while eating #{ e.cause.food }"
  puts e.cause.backtrace.first
end

To infinity and beyond!

ตัวอย่างข้างต้นลึกแค่หนึ่งระดับ แต่ nested exceptions ใน Ruby ลึกได้ไม่จำกัด ผมคิดว่าน่าแปลกใจถ้าคุณต้องลึกเกิน 3-4 ระดับ

...แต่เพื่อสนุก ลองทำ 100-level deep nested exception โค้ดขี้เล่นนี้ หวังว่าไม่เจอใน production


def recursively_raise(c=0)
  raise "Level #{ c }"
rescue => e
  if c < 100
    recursively_raise(c + 1)
  else
    recursively_print(e)
  end
end

def recursively_print(e)
  if e
    puts e
    recursively_print(e.cause)
  end
end

recursively_raise()
# ... Prints the following:
# Level 100
# Level 99
# Level 98
# etc.