Tôi đang trò chuyện với một người bạn về một đoạn mã Ruby & chủ đề về giá trị trả về xuất hiện…
…và đương nhiên, nil luôn là một phần của cuộc trò chuyện đó.
Sau đó trong ngày, tôi nằm trên giường sắp ngủ, suy nghĩ, tại sao chúng ta dùng nil?
Tôi nghĩ viết về chủ đề này vào ngày mai sẽ là ý kiến tốt, và đây là kết quả. Hy vọng bạn thấy thú vị & học được điều gì đó mới!
Về Giá trị Trả về & Hành vi Mong đợi
Khi bạn gọi phương thức như select bạn luôn sẽ nhận được một mảng.
Dù không có gì để chọn.
Ví dụ:
[1,2,3].select {} # []
[1,2,3].select(&:even?) # [2]
Nhưng tại sao điều này quan trọng?
Câu hỏi hay!
Nếu bạn luôn nhận được một mảng, bạn có thể gọi các phương thức mảng (size, empty?, push…) mà không phải kiểm tra xem bạn có thực sự đang làm việc với một mảng hay không.
Điều đó tốt vì kiểm tra class dẫn đến sự phụ thuộc cao (high coupling).
Nếu bạn muốn viết mã linh hoạt hơn hãy tránh kiểm tra “class này là gì” càng nhiều càng tốt.
Trả về Giá trị đơn lẻ
Ổn, nhưng phương thức như find, chỉ trả về một giá trị thì sao?
Nếu phần tử được tìm thấy thì nó chỉ hoạt động:
[1,2,3].find { |n| n == 2 } # 2
Nếu không khớp, find phải trả về cái gì đó, nó có thể trả về false, nhưng nếu chúng ta mong đợi gọi một phương thức trên giá trị trả về này thì chúng ta sẽ gặp ngoại lệ.
Đây là lúc nil xuất hiện, chúng ta dùng nó vì nó là thứ gần nhất với một Null Object chung (generic).
Như bạn có thể nhớ từ bài viết trước của tôi về nil, tôi đã chỉ cho bạn thấy nil là một class có các phương thức như to_i, to_s & to_a.
Điều này cho phép bạn sử dụng các phương thức như Array(), và String() để chuyển đổi một đối tượng thành các kiểu này.
Ví dụ:
String(123) # "123" String(nil) # "" Array(nil) # []
Điều này cũng xuất hiện trong nội suy chuỗi (string interpolation):
"abc#{nil}dfg"
Nội suy chuỗi gọi to_s trên kết quả của việc đánh giá cái bên trong khối nội suy. Vì vậy nếu bạn có #{1 + 1} điều này chuyển thành 2.to_s.
Bây giờ vấn đề với nil xuất hiện khi bạn không mong đợi nó, bạn nghĩ đó là một đối tượng phản hồi phương thức foo, nhưng nó không, nên bạn nhận được ngoại lệ NoMethodError đáng sợ.
Trong trường hợp đó, bạn muốn triển khai mẫu Null Object, như tôi mô tả trong bài viết này. Các giải pháp khác bao gồm sử dụng phương thức fetch với giá trị mặc định & ném ngoại lệ khi giá trị mặc định không có nghĩa.
Tóm tắt
Bạn đã học tại sao chúng ta dùng nil, cách tránh ngoại lệ từ việc gọi phương thức trên giá trị nil, và cách viết mã tự tin hơn.
Hãy nhìn vào mã của bạn & xem bạn có thể phát hiện mã kiểm tra kiểu nào (is_a? / kind_of? / ===). Làm thế nào bạn có thể cải thiện mã đó bằng cách sử dụng những gì bạn học hôm nay?