Ruby 安全調(diào)用運算符 (&.)

今天在閱讀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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容