Ruby sẽ cố ý ẩn một số lỗi & ngoại lệ (exceptions) khỏi bạn.
Đôi khi điều này hữu ích.
Như khi dùng phương thức Kernel#loop với block, loop sẽ dừng khi ngoại lệ StopIteration được ném ra.
Nhưng lần khác điều này có thể làm cho phiên bản debug (gỡ lỗi) của bạn khó khăn hơn nhiều.
Hãy xem vài ví dụ!
Ngoại lệ ẩn: Module Comparable + Phương thức <=>
Ví dụ đầu tiên liên quan đến module Comparable & phương thức <=>.
Đây là ví dụ:
class MyObject
attr_accessor :value
include Comparable
def initialize(value)
@value = value
end
def <=>(other)
raise ArgumentError, "can't compare #{other.class} with #{self.class}" unless other.is_a?(MyObject)
value <=> other.valuee
end
end
mo1 = MyObject.new(10)
mo2 = MyObject.new(10)
p mo1 == mo2
Hãy nói về ví dụ này.
Trước hết:
Chúng ta có class MyObject, với attr_accessor value, và include module Comparable, thêm các phương thức so sánh (như ==, <, >) vào class.
Các phương thức so sánh này dựa trên phương thức <=>.
Giống như phương thức Enumerable dựa trên each.
Sau đó:
Chúng ta tạo hai đối tượng (MyObject.new) với cùng giá trị (10).
Lưu ý ngay cả khi chúng có cùng giá trị chúng là đối tượng khác nhau, điều này quan trọng.
Bây giờ nếu so sánh hai đối tượng mo1 & mo2 chúng ta nhận false…
Tại sao?
Vì chúng ta có lỗi trong phương thức <=>, nhưng Ruby đang ẩn lỗi đó!
Nhìn kỹ…
Bạn có thể tìm ra lỗi không?
Nếu tìm thấy thì tốt! Nếu không cũng ok 🙂
Đây nó:
value <=> other.valuee
Thấy valuee này không?
Rốt cuộc chúng ta có lỗi gõ sai (typo)!
Bình thường chúng ta sẽ nhận NoMethodError & biết vấn đề khá nhanh. Nhưng không trong ví dụ này.
Tin tốt là từ Ruby 2.3 điều này đã thay đổi. Bây giờ bạn có thể thấy lỗi vì nó không còn bị ẩn.
Một lý do nữa để nâng cấp nếu bạn vẫn chạy Ruby phiên bản cũ.
Ngoại lệ ẩn: Đối tượng Numeric + Phương thức Coercion
Ví dụ khác của ngoại lệ ẩn là với đối tượng Numeric (Float, Integer) cộng với phương thức coerce.
Ví dụ:
class MyObject
attr_accessor :value
def initialize(value)
@value = value
end
def +(other)
other = MyObject.new(other) if other.kind_of?(Numeric)
value + other.value
end
def coerce(other)
mo = MyObject.new
mo.valuee = other
[mo, self]
end
end
mo1 = MyObject.new 10
mo2 = MyObject.new 10
p mo1 + mo2 # 20
p mo1 + 20 # 30
Đây là class MyObject khác, với phương thức mới + & coerce.
Phương thức coerce cho phép bạn làm việc với hai kiểu không tương thích & chuyển đổi chúng thành cùng kiểu để làm việc cùng nhau.
Trong trường hợp này class đại diện cho giá trị số, có thể là giây, giá hoặc bất cứ thứ gì…
Và chúng ta muốn làm các phép toán như:
mo1 + 20 20 + mo1
Cái đầu (mo1 + 20) dễ vì chúng ta kiểm soát phương thức + trong class.
Nhưng class Integer thì sao?
Chúng ta có thể thay đổi phương thức + của Integer để triển khai, nhưng đó có thể không phải ý kiến tốt 🙂
Giải pháp?
Triển khai phương thức coerce trong class của bạn.
Một điều phương thức + trên Integer sẽ làm là kiểm tra nếu đối tượng của bạn triển khai phương thức này, và nếu có nó sẽ gọi nó.
Bây giờ, nhớ ví dụ mã ở đầu phần này?
Nếu chúng ta thử làm:
20 + mo1
Chúng ta muốn thấy 30, vì giá trị mo1 là 10. Nhưng điều chúng ta thấy là:
MyObject can't be coerced into Fixnum
Cùng vấn đề như trước!
Một lỗi đang bị ẩn từ chúng ta bên trong phương thức coerce.
Đây: mo.valuee = other
Lại là typo, nhưng thông báo lỗi không nói điều đó!
MyObject can't be coerced into Fixnum
Tôi có tin tốt cho bạn, hành vi này thay đổi trong Ruby 2.5, vậy sẽ là một lỗi ẩn khác biến mất để bạn không phải lo lắng.
Tóm tắt
Như bạn thấy đây là ví dụ tốt về tại sao bạn muốn tránh ẩn ngoại lệ. Bạn có thể học thêm về ngoại lệ trong sách của tôi, Ruby Deep Dive.
Tôi sẽ trân trọng nếu bạn chia sẻ bài viết này với bạn bè để nhiều người hơn thấy.
Cảm ơn bạn đã đọc!