เวลาใช้ rescue clause ใน Ruby คุณระบุ exception classes ที่ต้องการ rescue ได้ แค่ให้รายการ classes:
begin
raise RuntimeError
rescue RuntimeError, NoMethodError
puts "rescued!"
end
แต่ถ้าคุณไม่รู้ exception class ตอนเขียนโค้ด ล่ะ? คำตอบง่ายคือ rescue ทุก exceptions ทดสอบ แล้ว re-raise ที่ไม่ผ่าน ประมาณนี้:
begin
raise "FUBAR! The ship's going down!"
rescue => e
raise unless e.message =~ /^FUBAR/
... do something ...
end
แต่นี่น่าเบื่อ! และไม่ DRY จะสนุกกว่าถ้าบอก rescue clause ให้ rescue แค่ exceptions ที่ match conditions และ Ruby ทำได้!
Rescue match exceptions ยังไง?
เวลา exception เกิดใน rescue block Ruby interpreter ตรวจสอบ exception class ต่อรายการ classes ที่คุณให้ ถ้า match exception จะถูก rescue
Matching ประมาณนี้:
exception_classes_to_rescue.any? do |c|
c === raised_exception.class
end
เหมือน operator ทุกตัวใน Ruby === เป็นเมธอดของ c ถ้าเรากำหนด === เอง จะทำอะไรได้บ้าง?
ตัวอย่างสร้างคลาส Anything ที่ Anything === x คืน true สำหรับค่า x ใดๆ ให้คลาสนี้เป็น argument ของ rescue จะ rescue ทุก exceptions:
class Anything
def self.===(exception)
true
end
end
begin
raise EOFError
rescue Anything
puts "It rescues ANYTHING!"
end
แม้มีวิธี rescue ทุก exceptions ดีกว่านี้ โค้ดนี้น่าสนใจเพราะแสดงสองอย่าง:
- ให้ rescue clause classes ที่ไม่ inherit จาก
Exceptionได้ ถ้ามี=== - ควบคุม
===ได้ = ควบคุมได้ว่า rescue exceptions ไหน
Rescue exceptions ตาม message
ตอนรู้แล้ว เขียนโค้ด rescue แค่ exceptions ที่ message match pattern ง่าย:
class AllFoobarErrors
def self.===(exception)
# rescue ทุก exceptions ที่ message เริ่มด้วย FOOBAR
exception.message =~ /^FOOBAR/
end
end
begin
raise EOFError, "FOOBAR: there was an eof!"
rescue AllFoobarErrors
puts "rescued!"
end
Rescue exceptions ตาม custom attributes
เพราะเข้าถึง exception object matcher ใช้ข้อมูลใดๆ ภายใน object ได้
จินตนาการว่าคุณมี exception ที่มี custom attribute "severity" อยากกลืน "low severity" แต่วิ่ง "high severity" ผ่าน implement ได้แบบนี้:
class Infraction < StandardError
attr_reader :severity
def initialize(severity)
@severity = severity
end
end
class LowSeverityInfractions
def self.===(exception)
exception.is_a?(Infraction) && exception.severity == :low
end
end
begin
raise Infraction.new(:low)
rescue LowSeverityInfractions
puts "rescued!"
end
ทำให้ Dynamic
ทั้งหมดคูลแต่มี boilerplate มาก มันมากเกินไปที่จะ define คลาส matcher เองทุกครั้ง เรา DRY ได้ด้วย metaprogramming นิดหน่อย
ตัวอย่างด้านล่าง define เมธอดสร้าง matcher classes ให้ คุณให้ matching logic ผ่าน block และ generator สร้างคลาสใหม่ที่ใช้ block ใน ===:
def exceptions_matching(&block)
Class.new do
def self.===(other)
@block.call(other)
end
end.tap do |c|
c.instance_variable_set(:@block, block)
end
end
begin
raise "FOOBAR: We're all doomed!"
rescue exceptions_matching { |e| e.message =~ /^FOOBAR/ }
puts "rescued!"
end
ข้อควรระวัง
เหมือนหลาย cool tricks ใน Ruby ผมตัดสินใจไม่ได้ว่านี่บ้าหรือไอเดียดี อาจเป็นทั้งสอง แม้ผมไม่แนะนำให้ใช้เทคนิคนี้เป็น first choice แต่เห็นประโยชน์ในสถานการณ์ เช่น rescue exceptions ตาม severity ในทุกกรณี มันเครื่องมืออีกชิ้นในชุดเครื่องมือของคุณ!