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

charleszhang · 2018年03月08日 · 最后由 1c7 回复于 2018年04月18日 · 8306 次阅读
本帖已被管理员设置为精华贴

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

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/

huacnlee 将本帖设为了精华贴。 03月08日 11:55

有了单身狗运算符,从此不再 try😂

charleszhang Rails 里 &. 这样的用法是什么意思 提及了此话题。 03月08日 13:11

谢谢分享!

哇 福音啊

https://ruby-china.org/topics/28555 单身狗运算符最开始是出自这里。火星了两年多了啊兄弟🙀

msg7086 回复

是的,以前一直不知道😂

感谢翻译。

try 是 Rails 的 API,不是 Ruby 里的

wuguzaliang 回复

ActiveSupport 里的,文中已有说明

如果能有一个选项默认所有调用都是安全调用就好了,我遇到的坑大部分都是 nil 调用方法的坑

感谢分享

看来大家都是先学的 Ruby。很多语言都有 ?. 运算符了。

感谢分享~~,我也是刚看 discourse 代码: https://github.com/discourse/discourse/blob/master/app/helpers/application_helper.rb#L270

def crawler_layout?
  controller&.use_crawler_layout?
end

也是没看明白 &. 是什么意思所以查了一下: 然后看到这篇文章:http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

原本想来 ruby china 分享下,原来早就有人写了哈哈。 (我最近 2018 年 4 月在定制 Discourse,不只是界面,界面 + 一些行为,所以要接触到源码,而不是写个 theme 就能搞定的事情,不是那么浅的定制)

spike76 类似于这种语法是什么意思? 提及了此话题。 08月10日 23:16
需要 登录 后方可回复, 如果你还没有账号请 注册新账号