Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Nâng cao Ruby Rescue: Khớp ngoại lệ động (Dynamic Exception Matching)

Khi bạn dùng mệnh đề rescue trong Ruby, bạn có thể chỉ định loại ngoại lệ muốn rescue. Chỉ cần cung cấp danh sách class ngoại lệ như sau:

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

Nhưng nếu bạn không biết class ngoại lệ sẽ là gì tại thời điểm viết code? Câu trả lời rõ ràng nhất là rescue tất cả ngoại lệ, thực hiện test nào đó và re-raise những ngoại lệ không pass. Như thế này:

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

Nhưng đó quá nhàm chán! Thêm vào đó, nó không phải cách DRY. Sẽ thú vị hơn nếu chúng ta có thể nói với mệnh đề rescue chỉ rescue những ngoại lệ khớp điều kiện của chúng ta. Và vì đây là Ruby, chúng ta có thể làm được!

Rescue khớp ngoại lệ như thế nào

Khi ngoại lệ xảy ra bên trong rescue block, Ruby interpreter kiểm tra class của ngoại lệ so với danh sách class ngoại lệ bạn cung cấp. Nếu khớp, ngoại lệ được rescue.

Việc khớp trông như sau:

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

Giống như mọi toán tử khác trong Ruby, === chỉ là một phương thức. Trong trường hợp này là phương thức của c. Vậy nếu chúng ta định nghĩa phương thức === riêng thì sao?

Trong ví dụ dưới, tôi tạo class Anything mà Anything === x trả về true cho bất kỳ giá trị x nào. Nếu tôi đưa class này làm đối số cho rescue, nó khiến tất cả ngoại lệ được rescue.

class Anything def self.===(exception) true end end begin raise EOFError rescue Anything puts "It rescues ANYTHING!" end 

Mặc dù có cách tốt hơn để rescue tất cả ngoại lệ, code này thú vị vì nó cho thấy hai điều:

  1. Bạn có thể đưa cho rescue clause các class không kế thừa từ Exception, miễn là chúng implement ===

  2. Nếu bạn kiểm soát ===, bạn kiểm soát được ngoại lệ nào được rescue.

Rescue ngoại lệ dựa trên thông điệp

Biết điều trên, viết code chỉ rescue ngoại lệ nếu thông điệp khớp pattern rất đơn giản.

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 

Rescue ngoại lệ dựa trên thuộc tính tùy chỉnh

Vì bạn có quyền truy cập đối tượng ngoại lệ, matcher của bạn có thể dùng bất kỳ dữ liệu nào bên trong đối tượng đó.

Hãy tưởng tượng bạn có ngoại lệ với thuộc tính tùy chỉnh "severity". Bạn muốn nuốt chửng tất cả ngoại lệ "low severity" nhưng để pass các "high severity". Bạn có thể implement như sau:

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 

Làm nó động (Dynamic)

Tất cả trên khá cool, nhưng liên quan đến nhiều boilerplate code. Việc phải định nghĩa thủ công class matcher riêng cho từng matcher có vẻ quá mức. May mắn thay chúng ta có thể DRY lên khá nhiều bằng chút metaprogramming.

Trong ví dụ dưới, chúng ta định nghĩa phương thức generate matcher class cho chúng ta. Bạn cung cấp logic matching qua block, và matcher generator tạo class mới dùng block bên trong phương thức === của nó.

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 

Một chút muối

Giống nhiều thủ thuật cool trong Ruby, tôi không quyết định được tất cả này là điên rồ hay ý tưởng tuyệt vời. Có lẽ cả hai. Mặc dù tôi chắc chắn không khuyên bạn dùng kỹ thuật này làm lựa chọn đầu tiên, tôi thấy nó hữu ích trong tình huống như trên mà bạn muốn rescue ngoại lệ dựa trên severity. Trong mọi trường hợp, đây là công cụ thêm vào toolbelt!