หากเคยทำงานกับ Ruby exceptions รู้ว่าระบุได้ว่า exception ไหนถูก rescue ไหนไม่:
begin
raise ArgumentError
rescue ArgumentError
# Rescue `ArgumentError`
end
...และน่าจะรู้ว่า rescue "parent" จะ rescue "children" ทั้งหมดด้วย
begin
raise ArgumentError
rescue StandardError
# Rescue `ArgumentError` เพราะมันสืบทอดจาก `StandardError`
end
พูด "parent" และ "child" หมายถึง class inheritance ที่ลึกใน Ruby source code มีอย่างนี้:
class ArgumentError < StandardError
...
end
เทคนิคน่าสนใจ
คำถาม: Ruby รู้ยังไงว่า exception ใดๆ สืบทอดจากคลาสที่ระบุ?
วิชีวมากที่สุดคือใช้เมธอด is_a? หรือ kind_of? ทưởng像เป็น:
if the_exception.is_a?(StandardError)
# do the rescue
end
แต่ไม่ได้เป็นอย่างนั้น Ruby ใช้ตัวดำเนินการ === ที่น่าสนใจกว่า
if StandardError === the_exception
# do the rescue
end
หากไม่เคยใช้ a === b มันมักตอบคำถาม "a อยู่ในกลุ่มที่ b กำหนดหรือไม่?" ตัวอย่าง:
(1..10) === 5 # true
('a'..'f') === "z" # false
String === "hello" # true
String === 1 # false
/[0-9]{3}/ === "hello123" # true
/[0-9]{3}/ === "hello" # false
เพราะ === เป็นเมธอด Ruby ธรรมดาเหมือน == เราสามารถ define เองได้:
class RedThings
def self.===(thing)
thing.color == :red
end
end
สิ่งที่เรารู้: rescue ใช้ === กำหนดว่า exception ไหนถูก rescue และเรา define === เองได้ หมายถึงเราสร้างคลาสที่ตัดสินใจ on-the-fly ว่า exception ไหนถูก rescue:
class SevereMatcher
def self.===(exception)
exception.message =~ /severe/
end
end
begin
raise RuntimeError, "Something severe happened"
rescue SevereMatcher
# rescue ทุก exceptions ที่มีคำว่า "severe" ใน message
# ไม่สนใจ class
end
รู้เทคนิคนี้แล้ว ขีดจำกัดคือจินตนาการ
สรุป
ยอมรับ: อาจไม่ต้องสร้าง dynamic exception matcher แต่ตัวอย่างน่าสนใจว่า implementation detail เล็กๆ อย่างใช้ === แทน kind_of? ทำให้ Ruby ยืดหยุ่นและน่าสนใจมากขึ้น