Ruby 安全调用运算符 (&.)

今天在阅读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 #

account && account.owner && account.owner.address
# => NoMethodError: undefined method `address' for #`

account.try(:owner).try(:address)
# => nil

account&.owner&.address
# => NoMethodError: undefined method `address' for #`

很遗憾,try方法没有检查接收者是否响应给定的方法。这就是为什么应该尽量使用try的严格版本——try!的原因:

account.try!(:owner).try!(:address)
# => NoMethodError: undefined method `address' for #`

注意事项

这段作者也有疑惑,我就自己发挥一下了。第一个例子不用解释。第二个我理解为先调用nil?,此时返回false(主对象main调用nil?的结果)。然后再调用nil?,还是返回false(相当于false.nil?)。第三个例子表明&.符号在对象为nil时不会再去检查是否响应对应的方法

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)

写在后面

我非常不喜欢处理动态语言中的空值,安全调用运算符和dig的出现使得代码可以写得非常简洁。(最后一句不翻了,Ruby 2.3.0早已发布,祝大家编码愉快^_^

英文原文地址:http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

你可能感兴趣的:(Ruby 安全调用运算符 (&.))