Computer >> Máy Tính >  >> Lập trình >> Ruby

Lên cấp `cứu` với trình đối sánh ngoại lệ động

Khi bạn sử dụng mệnh đề giải cứu trong Ruby, bạn có thể chỉ định loại ngoại lệ nào bạn muốn giải cứu. Tất cả những gì bạn cần làm là cung cấp danh sách các lớp ngoại lệ như sau:

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

Nhưng điều gì sẽ xảy ra nếu bạn không biết lớp ngoại lệ sẽ là gì tại thời điểm bạn viết mã? Câu trả lời rõ ràng nhất là giải cứu tất cả các ngoại lệ, thực hiện một số loại kiểm tra và sau đó nâng cao lại các ngoại lệ không vượt qua. Một cái gì đó như thế này:

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

Nhưng chán quá! Thêm vào đó, nó không phải là một cách tiếp cận rất KHÔ. Sẽ thú vị hơn rất nhiều nếu bằng cách nào đó chúng ta có thể nói với điều khoản giải cứu là chỉ giải cứu những trường hợp ngoại lệ phù hợp với điều kiện của chúng ta. Và vì đây là Ruby, chúng ta có thể làm được!

Cách giải cứu khớp với các trường hợp ngoại lệ

Khi một ngoại lệ xảy ra bên trong khối cứu hộ, trình thông dịch ruby ​​sẽ kiểm tra lớp của ngoại lệ so với danh sách các lớp ngoại lệ mà bạn đã cung cấp. Nếu có sự trùng khớp, ngoại lệ sẽ được giải cứu.

Kết hợp trông giống như sau:

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

Cũng giống như mọi toán tử khác trong Ruby, === chỉ đơn giản là một phương pháp. Trong trường hợp này, đó là một phương thức của c . Vì vậy, chúng tôi có thể làm gì nếu chúng tôi xác định === của riêng mình phương pháp?

Trong ví dụ dưới đây, tôi đang tạo một lớp có tên là Anything ở đâu Anything === x trả về true cho bất kỳ giá trị nào của x. Nếu tôi cung cấp lớp này như một đối số để giải cứu, nó sẽ khiến tất cả các trường hợp ngoại lệ được giải cứu.

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

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

Mặc dù có nhiều cách tốt hơn để giải cứu tất cả các trường hợp ngoại lệ, nhưng mã này rất thú vị vì nó cho chúng ta thấy hai điều:

  1. Bạn có thể cung cấp cho các lớp mệnh đề cứu hộ không kế thừa từ Exception , miễn là họ triển khai ===

  2. Nếu bạn kiểm soát === , bạn có thể kiểm soát trường hợp ngoại lệ nào được cứu.

Giải cứu các trường hợp ngoại lệ dựa trên thông báo

Biết những gì chúng ta biết bây giờ, thật đơn giản để viết mã chỉ giải cứu các ngoại lệ nếu thông báo của ngoại lệ khớp với một mẫu.

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

Giải cứu các trường hợp ngoại lệ dựa trên các thuộc tính tùy chỉnh

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

Hãy tưởng tượng trong giây lát rằng bạn có một ngoại lệ có thuộc tính tùy chỉnh được gọi là "mức độ nghiêm trọng". Bạn muốn xóa tất cả các lần xuất hiện "mức độ nghiêm trọng thấp" của ngoại lệ, nhưng hãy bỏ qua bất kỳ lần nào "mức độ nghiêm trọng cao". Bạn có thể triển khai như vậy:

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 cho nó năng động

Tất cả những điều này đều khá tuyệt, nhưng nó liên quan đến rất nhiều mã soạn sẵn. Có vẻ như quá mức khi phải xác định thủ công các lớp riêng biệt cho mỗi trình kết hợp. May mắn thay, chúng ta có thể KHÔ điều này một chút bằng cách sử dụng một chút lập trình siêu hình.

Trong ví dụ dưới đây, chúng tôi đang xác định một phương thức tạo các lớp đối sánh cho chúng tôi. Bạn cung cấp logic đối sánh thông qua một khối và trình tạo đối sánh tạo ra một lớp mới sử dụng khối bên trong === của nó phương pháp.

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 hạt muối

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