今天在閱讀discourse源碼時,看到其中有很多地方使用&.符號,如:
result << types[:whisper] if viewed_by&.staff?
不明白干啥的,搜索了一下,發(fā)現(xiàn)國外一篇文章總結(jié)的很好,在此記錄一下。
安全調(diào)用運算符(&.)是Ruby 2.3.0中最吸引人的新增特性之一。類似的運算符在C#和Groovy語言中早已存在,用的語法略有不同,是符號(?.)。那么這個運算符的作用是什么呢?
使用場景
假設(shè)有一個account對象,它有一個關(guān)聯(lián)的owner對象,現(xiàn)在想要獲取owner的address屬性。穩(wěn)妥的不引發(fā)nil異常的寫法如下:
if account && account.owner && account.owner.address
...
end
這種寫法很啰嗦、很不爽。ActiveSupport提供的try方法有類似的寫法(兩者稍微有一點區(qū)別,我們稍后討論):
if account.try(:owner).try(:address)
...
end
這段代碼完成同樣的功能——它要么返回address字段值要么返回nil(當(dāng)調(diào)用鏈中有nil值時)。但第一個例子的寫法也有可能返回false,比如當(dāng)owner值為false的時候。
使用&.
我們可以使用安全調(diào)用運算符重寫前面的例子:
account&.owner&.address
雖然語法看起來有點奇怪,但我猜你會很快愛上它,因為它的確讓代碼更簡潔。
更多例子
接下來讓我們詳細比較一下這三種方式:
account = Account.new(owner: nil) # account without an owner
account.owner.address
# => NoMethodError: undefined method `address' for nil:NilClass
account && account.owner && account.owner.address
# => nil
account.try(:owner).try(:address)
# => nil
account&.owner&.address
# => nil
這個例子沒有什么特別的。但如果owner的值為false呢(雖然可能性不大,但在垃圾代碼的狂躁世界里并非不可能)?
account = Account.new(owner: false)
account.owner.address
# => NoMethodError: undefined method `address' for false:FalseClass `
account && account.owner && account.owner.address
# => false
account.try(:owner).try(:address)
# => nil
account&.owner&.address
# => undefined method `address' for false:FalseClass`
第一個亮點出現(xiàn)了——&.運算符只跳過nil但可以識別false!它與s1 && s1.s2 && s1.s2.s3這種寫法并不完全一樣。
如果owner不為空但是并沒有address屬性(方法)呢?
account = Account.new(owner: Object.new)
account.owner.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>
account && account.owner && account.owner.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
account.try(:owner).try(:address)
# => nil
account&.owner&.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
很遺憾,try方法沒有檢查接收者是否響應(yīng)給定的方法。這就是為什么應(yīng)該盡量使用try的嚴格版本——try!的原因:
account.try!(:owner).try!(:address)
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
注意事項
這段作者也有疑惑,我就自己發(fā)揮一下了。第一個例子不用解釋。第二個我理解為先調(diào)用nil?,此時返回false(主對象main調(diào)用nil?的結(jié)果)。然后再調(diào)用nil?,還是返回false(相當(dāng)于false.nil?)。第三個例子表明&.符號在對象為nil時不會再去檢查是否響應(yīng)對應(yīng)的方法。
nil.nil?
# => true
nil?.nil?
# => false
nil&.nil?
# => nil
Array#dig and Hash#dig
在我看來,dig方法是這個版本中最有用的特性。我們再也不用寫下面這樣惡心的代碼:
address = params[:account].try(:[], :owner).try(:[], :address)
# or
address = params[:account].fetch(:owner) .fetch(:address)
只需簡單的使用Hash#dig來達到同樣的目的:
address = params.dig(:account, :owner, :address)
寫在后面
我非常不喜歡處理動態(tài)語言中的空值,安全調(diào)用運算符和dig的出現(xiàn)使得代碼可以寫得非常簡潔。(最后一句不翻了,Ruby 2.3.0早已發(fā)布,祝大家編碼愉快^_^)
英文原文地址:http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/