Ruby: Toán tử ||= và defined?

Trong phần lớn các ứng dụng Rails hoặc thậm chí là các gem Ruby, bạn có thể tìm thấy rất nhiều trường hợp bạn cần ghi nhớ kết quả của một hàm để tăng tốc ứng dụng của bạn khi mà hàm đó đã được tính toán. Giải pháp được sử dụng phổ biến nhất có lẽ là sử dụng toán tử ||=. Ví dụ: @result ||= do_some_heavy_computation Tuy nhiên, có một số trường hợp mà nó không phù hợp, khi đó bạn nên sử dụng toán tử defined?

Toán tử ||= là gì?

Hãy quay lại ví dụ từ phần giới thiệu: @result ||= do_some_heavy_computation. Toán tử ||= nó hoạt động như thế nào? Nó gần giống với @result || @result = do_some_heavy_computation nhưng không hoàn toàn giống nhau mà hiểu là: "trả về giá trị của @result nếu giá trị là đúng hoặc gán kết quả của do_some_heavy_computation cho @result". Rõ ràng, phiên bản ngắn gọn này có vẻ hấp dẫn hơn. Nhưng hãy nhớ rằng, toán tử này không hẳn là gán giá trị cho biến mà là kiểm tra giá trị của biến đúng hay không. Vậy làm sao để kiểm tra 1 biến, trả ra nil khi nó chưa được định nghĩa và không gặp phải ngoại lệ nào?

Toán tử define? là gì

Chúng ta có thể giải quyết vấn đề trên đây bằng việc sử dụng toán tử defined?, nó trả về nil nếu đối số chưa được định nghĩa, nếu đã được định nghĩa, nó trả về mô tả của đối số. Với defined? chúng ta có thể dễ dàng kiểm tra biến đã có hay chưa. Ví dụ:

defined?(@result) // => nil

@result = nil
defined?(@result) // => "instance-variable"

Ghi nhớ

Vậy là chúng ta đã hiểu sự khác nhau giữa 2 toán tử ||=defined?. Hãy tưởng tượng bạn có 1 hàm như sau:

def heavy_computation_result
  @result ||= do_some_heavy_computation
end

và bạn phải gọi heavy_computation_result nhiều lần để sử dụng kết quả của nó. Hãy xem hàm tính toán này rất nặng (như cái tên của nó 😄) và nó chỉ nên được tính một lần vì lý do hiệu suất. Điều gì xảy ra nếu tính toán này trả về nil hoặc false?

@result ||= do_some_heavy_computation hoạt động theo một cách khá giống với @result || @result = do_some_heavy_computation, nếu phía trái là false thì và tính toán sẽ được thực hiện mỗi khi bạn gọi phương thức heavy_computation_result, vậy nó làm cho cú pháp này vô dụng ở đây!

Để ghi nhớ đúng, hàm này nên được viết lại bằng cách sử dụng toán tử defined? như sau:

def heavy_computation_result
  return @result if defined?(@result)
  @result = do_some_heavy_computation
end

Tổng kết

Mặc dù toán tử ||= thường được sử dụng để ghi nhớ, nhưng nó không phải là giải pháp tốt nhất cho vấn đề này. Nó khá thuận tiện để sử dụng, tuy nhiên, khi có một hàm trả ra giá trị như falsenil, sử dụng defined? sẽ an toàn hơn nhiều.

Nguồn: Karol Galanciak Blog