Chắc hẳn khi làm việc với Rails, để gọi method của object mà không cần sợ object đó có nil hay không hoặc để handle lỗi NoMethodError: undefined method for nil:NilClass
, 1 trong top những lỗi kinh điển nhất của Rails, chúng ta thường sẽ nghĩ tới method try
hay Safe Navigation Operator (&.)
như 1 cách để giải quyết vấn đề. Ví dụ:
1
2
3
4
post = nil
post.title # => NoMethodError: undefined method `title' for nil:NilClass
post.try(:title) # => nil
post&.title # => nil
Nhìn có vẻ giống nhau nhưng sẽ có 1 chút khác biệt đó
So sánh
1. &.
cơ bản thì ngắn hơn try
=)))
Code ngắn, dễ đọc :v
2. &.
của Ruby, try
của Rails
Method try
không phải là method trong Ruby core mà là method từ activesupport. Do đó khi không code trong Rails app (ví dụ viết script tương tự Bash) thì try
không sử dụng được.
3. &.
dễ debug hơn
Giả sử mình gọi 1 method nào đó không tồn tại trong app.
1
2
'foo'&.non_existent # => NoMethodError: undefined method `non_existent' for "foo":String
'foo'.try(:non_existent) # => nil
** Tương tự nếu object là false
Chỉ khi object đó là nil
thì 2 thằng này giống nhau thôi.
1
2
nil&.non_existent # => nil
nil.try(:non_existent) # => nil
Tưởng tượng là mình vừa đổi tên method từ abc
=> non_existent
nhưng mình lại quên nó đi thì &.
sẽ đóng vai trò là 1 người cứu tinh giúp bạn bớt bạc tóc để khỏi tìm xem tại sao lại có exception. Trái lại, try
sẽ xoa dịu cơn đau đầu bằng kết quả trả về là nil
và bạn sẽ nghĩ là code trông ổn đó, không bug
. Vậy thì dùng &.
ổn hơn chứ nhờ.
4. &.
nhanh hơn try
Theo kết quả lượm nhặt được từ bài gốc:
1
2
3
4
5
6
require 'active_support/all'
require 'benchmark'
foo = nil
puts Benchmark.measure { 10_000_000.times { foo.try(:lala) } }
puts Benchmark.measure { 10_000_000.times { foo&.lala } }
Output
1
2
1.310000 0.000000 1.310000 ( 1.311835)
0.360000 0.000000 0.360000 ( 0.353127)
=> &.
nhanh hơn try
gần 4 lần.
Tóm lại
try
nên được dùng khi bạn không đảm bảo method được gọi có tồn tại hay không&.
nên được dùng khi bạn không đảm bảo object có bị nil hay khôngtry
có thể sử dụng thay thế cho&.
1 2 3 4
post = nil post.title # => NoMethodError: undefined method `title' for nil:NilClass post&.title # => nil post.try(:title) # => nil
&.
không thể sử dụng thay thế chotry
1 2 3 4 5
post = Post.first # => <Post:0x00000000060a46e0 id: nil, title: "Ahihi"> post.title # => "Ahihi" post.content # => NoMethodError: undefined method `content' for #<Post> post.try(:content) # => nil post&.content # NoMethodError: undefined method `content' for #<Person>