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

ยกระดับ `กู้ภัย' ด้วยตัวจับคู่ข้อยกเว้นแบบไดนามิก

เมื่อคุณใช้คำสั่งช่วยเหลือใน Ruby คุณสามารถระบุประเภทของข้อยกเว้นที่คุณต้องการช่วยเหลือได้ สิ่งที่คุณต้องทำคือจัดเตรียมรายการคลาสข้อยกเว้นดังนี้:

begin
  raise RuntimeError
rescue RuntimeError, NoMethodError
  puts "rescued!"
end

แต่ถ้าคุณไม่ทราบว่าคลาสข้อยกเว้นจะเป็นอย่างไรในขณะที่คุณเขียนโค้ด คำตอบที่ชัดเจนที่สุดคือการช่วยเหลือข้อยกเว้นทั้งหมด ทำการทดสอบบางประเภท จากนั้นจึงเพิ่มข้อยกเว้นที่ไม่ผ่านขึ้นใหม่ บางสิ่งเช่นนี้:

begin
  raise "FUBAR! The ship's going down!"
rescue => e
  raise unless e.message =~ /^FUBAR/
  ... do something ...
end

แต่มันน่าเบื่อมาก! นอกจากนี้ยังไม่ใช่แนวทางที่แห้งมาก คงจะน่าสนใจกว่านี้มากถ้าเราสามารถบอกคำสั่งกู้ภัยเพื่อช่วยเหลือข้อยกเว้นที่ตรงกับเงื่อนไขของเราเท่านั้น และเนื่องจากนี่คือ Ruby เราทำได้!

กู้ภัยตรงกับข้อยกเว้นอย่างไร

เมื่อมีข้อยกเว้นเกิดขึ้นภายในบล็อกการกู้ชีพ ตัวแปล ruby ​​จะตรวจสอบคลาสของข้อยกเว้นกับรายการคลาสข้อยกเว้นที่คุณระบุ หากมีการแข่งขัน ข้อยกเว้นจะได้รับการช่วยเหลือ

การจับคู่จะมีลักษณะดังนี้:

exception_classes_to_rescue.any? do |c|
  c === raised_exception.class
end

เช่นเดียวกับโอเปอเรเตอร์อื่นๆ ใน Ruby === เป็นเพียงวิธีการ ในกรณีนี้เป็นวิธีการของ c . แล้วเราจะทำอย่างไรถ้าเรากำหนด === . ของเราเอง วิธีการ?

ในตัวอย่างด้านล่าง ฉันกำลังสร้างคลาสชื่อ Anything โดยที่  Anything === x คืนค่า  จริง สำหรับค่า x ใดๆ ถ้าฉันให้ชั้นเรียนนี้เป็นข้อโต้แย้งในการช่วยเหลือ มันจะทำให้ข้อยกเว้นทั้งหมดได้รับการช่วยเหลือ

class Anything
  def self.===(exception)
    true
  end
end

begin
  raise EOFError
rescue Anything
  puts "It rescues ANYTHING!"
end

แม้ว่าจะมีวิธีที่ดีกว่ามากในการกู้คืนข้อยกเว้นทั้งหมด แต่โค้ดนี้น่าสนใจเพราะแสดงให้เราเห็นสองสิ่ง:

  1. คุณสามารถให้คลาสของคำสั่งช่วยเหลือที่ไม่สืบทอดจาก Exception ตราบใดที่พวกเขาใช้ ===

  2. หากคุณควบคุม === คุณสามารถควบคุมข้อยกเว้นที่จะได้รับการช่วยเหลือ

การช่วยเหลือข้อยกเว้นตามข้อความ

เมื่อรู้ว่าเรารู้อะไรในตอนนี้ การเขียนโค้ดก็ง่ายที่จะกู้คืนข้อยกเว้นได้ก็ต่อเมื่อข้อความของข้อยกเว้นตรงกับรูปแบบ

class AllFoobarErrors
  def self.===(exception)
    # rescue all exceptions with messages starting with FOOBAR 
    exception.message =~ /^FOOBAR/
  end
end

begin
  raise EOFError, "FOOBAR: there was an eof!"
rescue AllFoobarErrors
  puts "rescued!"
end

การช่วยเหลือข้อยกเว้นตามแอตทริบิวต์ที่กำหนดเอง

เนื่องจากคุณมีสิทธิ์เข้าถึงอ็อบเจ็กต์ข้อยกเว้น ตัวจับคู่ของคุณจึงสามารถใช้ข้อมูลใดก็ได้ที่อยู่ในออบเจกต์นั้น

ลองนึกภาพสักครู่ว่าคุณมีข้อยกเว้นที่มีแอตทริบิวต์ที่กำหนดเองที่เรียกว่า "ความรุนแรง" คุณต้องการกลืน "ความรุนแรงต่ำ" ที่เกิดข้อยกเว้นทั้งหมด แต่ปล่อยให้ผ่านเหตุการณ์ "ความรุนแรงสูง" ใดๆ ไป คุณอาจนำไปใช้เช่นนั้น:

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

ทำให้เป็นไดนามิก

ทั้งหมดนี้ค่อนข้างเจ๋ง แต่มันเกี่ยวข้องกับรหัสสำเร็จรูปจำนวนมาก ดูเหมือนว่ามากเกินไปที่จะต้องกำหนดคลาสแยกกันสำหรับตัวจับคู่แต่ละตัว โชคดีที่เราสามารถทำให้สิ่งนี้แห้งได้เล็กน้อยโดยใช้โปรแกรมเมตาดาต้าเพียงเล็กน้อย

ในตัวอย่างด้านล่าง เรากำลังกำหนดวิธีการที่สร้างคลาสที่จับคู่สำหรับเรา คุณระบุตรรกะการจับคู่ผ่านบล็อก และตัวสร้างการจับคู่จะสร้างคลาสใหม่ที่ใช้บล็อกภายใน === วิธีการ

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

เม็ดเกลือ

เช่นเดียวกับลูกเล่นเจ๋ง ๆ มากมายใน Ruby ฉันไม่สามารถตัดสินใจได้ทั้งหมดว่าทั้งหมดนี้เป็นเรื่องบ้าหรือเป็นความคิดที่ดี บางทีมันอาจจะเล็กน้อยของทั้งสอง แม้ว่าฉันจะไม่แนะนำให้คุณใช้เทคนิคนี้เป็นตัวเลือกแรก แต่ฉันสามารถเห็นได้ว่าเทคนิคนี้จะมีประโยชน์อย่างไรในสถานการณ์เช่นสถานการณ์ข้างต้นที่คุณต้องการช่วยเหลือข้อยกเว้นตามความรุนแรง เป็นอีกเครื่องมือหนึ่งในแถบเครื่องมือของคุณ!