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:
-
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===
-
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!