今天在阅读 discourse 源码时,看到其中有很多地方使用&.
符号,如:
result << types[:whisper] if viewed_by&.staff?
不明白干啥的,搜索了一下,发现国外一篇文章总结的很好,在此记录一下。
安全调用运算符(&.)是 Ruby 2.3.0中最吸引人的新增特性之一。类似的运算符在C#和Groovy语言中早已存在,用的语法略有不同,是符号(?.)。那么这个运算符的作用是什么呢?
假设有一个 account 对象,它有一个关联的 owner 对象,现在想要获取 owner 的 address 属性。稳妥的不引发 nil 异常的写法如下:
if account && account.owner && account.owner.address
...
end
这种写法很啰嗦、很不爽。ActiveSupport 提供的 try 方法有类似的写法(两者稍微有一点区别,我们稍后讨论):
if account.try(:owner).try(:address)
...
end
这段代码完成同样的功能——它要么返回 address 字段值要么返回 nil(当调用链中有 nil 值时)。但第一个例子的写法也有可能返回 false,比如当 owner 值为 false 的时候。
我们可以使用安全调用运算符重写前面的例子:
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`
第一个亮点出现了——&.运算符只跳过 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 方法没有检查接收者是否响应给定的方法。这就是为什么应该尽量使用 try 的严格版本——try! 的原因:
account.try!(:owner).try!(:address)
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
这段作者也有疑惑,我就自己发挥一下了。第一个例子不用解释。第二个我理解为先调用 nil?,此时返回 false(主对象 main 调用 nil?的结果)。然后再调用 nil?,还是返回 false(相当于 false.nil?)。第三个例子表明&.符号在对象为 nil 时不会再去检查是否响应对应的方法。
nil.nil?
# => true
nil?.nil?
# => false
nil&.nil?
# => nil
在我看来,dig 方法是这个版本中最有用的特性。我们再也不用写下面这样恶心的代码:
address = params[:account].try(:[], :owner).try(:[], :address)
# or
address = params[:account].fetch(:owner) .fetch(:address)
只需简单的使用Hash#dig来达到同样的目的:
address = params.dig(:account, :owner, :address)
我非常不喜欢处理动态语言中的空值,安全调用运算符和 dig 的出现使得代码可以写得非常简洁。(最后一句不翻了,Ruby 2.3.0 早已发布,祝大家编码愉快^_^
)
英文原文地址:http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/