Nếu bạn đã làm việc với exception của Ruby trước đây, bạn biết bạn có thể chỉ định exception nào được rescue và nào không:
begin
raise ArgumentError
rescue ArgumentError
# Rescues the `ArgumentError`
end
...và bạn có thể biết rằng khi bạn rescue một "parent", bạn rescue tất cả "children" của nó cũng vậy.
begin
raise ArgumentError
rescue StandardError
# Rescues `ArgumentError`, vì nó kế thừa từ
# `StandardError`
end
Khi tôi nói "parent" và "child", tôi chỉ đơn giản đề cập đến class inheritance. Ở đâu đó sâu trong source Ruby có thứ tương đương với:
class ArgumentError < StandardError
...
end
Một mẹo thú vị
Câu hỏi của tôi: làm sao Ruby biết nếu bất kỳ exception cho trước nào kế thừa từ class bạn chỉ định?
Cách tiếp cận rõ ràng nhất sẽ là dùng phương thức is_a? hoặc kind_of?. Chúng ta có thể tưởng tượng nó trông như:
if the_exception.is_a?(StandardError)
# do the rescue
end
Nhưng đó không phải điều xảy ra. Thay vào đó, Ruby dùng toán tử === thú vị hơn.
if StandardError === the_exception
# do the rescue
end
Nếu bạn chưa bao giờ dùng a === b, nó thường trả lời câu hỏi "does a inherently belong to the group defined by b"? Đây là một số ví dụ:
(1..10) === 5 # true
('a'..'f') === "z" # false
String === "hello" # true
String === 1 # false
/[0-9]{3}/ === "hello123" # true
/[0-9]{3}/ === "hello" # false
Vì === chỉ là một phương thức Ruby bình thường như ==, chúng ta có thể tự định nghĩa nó:
class RedThings
def self.===(thing)
thing.color == :red
end
end
Vậy, chúng ta biết gì? Chúng ta biết rescue dùng === để xác định exception nào được rescue. Và chúng ta biết chúng ta có thể tự định nghĩa === method. Nghĩa là chúng ta có thể tạo một class quyết định on-the-fly exception nào được rescue:
class SevereMatcher
def self.===(exception)
exception.message =~ /severe/
end
end
begin
raise RuntimeError, "Something severe happened"
rescue SevereMatcher
# rescues tất cả exception có từ "severe" trong
# message, bất kể class.
end
Khi bạn biết mẹo này, giới hạn duy nhất là trí tưởng tượng của bạn.
Kết luận
Tôi thừa nhận: bạn có thể không bao giờ cần tạo một dynamic exception matcher. Nhưng đây là ví dụ thực sự thú vị về cách một chi tiết triển khai tưởng chừng như tầm thường như dùng === thay vì kind_of? làm cho Ruby linh hoạt và thú vị hơn nhiều.