天天看点

《Effective Ruby:改善Ruby程序的48条建议》一第2条:所有对象的值都可能为nil

本节书摘来自华章出版社《effective ruby:改善ruby程序的48条建议》一书中的第1章,第1.2节,作者[美]彼得 j.琼斯(peter j. jones),更多章节内容可以访问云栖社区“华章计算机”公众号查看

运行的ruby程序中的每个对象都源自同一个类,即以某种方式继承自类basicobject。试想这一个个相互关联的对象是怎样以basicobject为根节点构成一个熟悉的属性图的。在实践中,这意味着一个类的对象能够被另一个类的对象替换(感谢多态)。这就是我们能将一个行为类似数组却又不真的是数组的对象传递给一个以array对象为预期参数的方法的原因。ruby程序员喜欢称之为“鸭子类型”(duck typing)。与其要求对象是某个给定类的实例,不如将注意力放在该对象能做什么上;换句话说,接口高于类型。用ruby的术语来说,鸭子类型意味着,相比is_a?方法你更喜欢使用respond_to?方法。

不过实际中很少见到有方法通过使用respond_to?进行参数检查来保证其使用正确的接口。作为替代,我们更倾向于直接调用对象的方法,在对象没有该方法时,让ruby自己在运行时触发nomethoderror异常。表面上看,这似乎给ruby程序员带来了真正的问题。好吧,是这样的,只你我二人知道。这也是测试如此重要的原因。没有什么会阻止你意外地将time类型对象传递给接收date对象的方法。我们需要通过优秀的测试挑出各种各样的错误。感谢测试,这些类型的问题是可以通过测试避免的。但即使这样,这些多态替换也可能使经过测试的应用程序出现问题:

《Effective Ruby:改善Ruby程序的48条建议》一第2条:所有对象的值都可能为nil

当你调用一个对象的方法而其返回值刚好是讨厌的nil对象时,这种情况就会发生……nil是类nilclass的唯一对象。这样的错误会悄然逃过测试而仅在生产环境下出现:如果一个用户做了些超乎寻常的事情。另一种导致该结果的情况是,当一个方法返回nil并将其作为参数直接传给另一个方法时。事实上存在数量惊人的方式可以将nil意外地引入你运行中的程序。最好的防范方式是:假设任何对象都可以为nil,包括方法参数和调用方法的返回值。

避免在nil对象上调用方法的最简单的方式是使用nil?方法。如果方法接收者(receiver)是nil,该方法将返回真值,否则返回假值。当然,nil对象在boolean上下文中总是假值,因此if和unless表达式可以如你所期望的那样工作。以下几行代码是等

价的:

《Effective Ruby:改善Ruby程序的48条建议》一第2条:所有对象的值都可能为nil

将变量显式转换为期望的类型常常比时刻担心其为nil要容易得多。尤其是在一个方法即使是部分输入为nil时也应该产生结果的时候。object类定义了几种转换方法,它们能在这种情况下派上用场。比如,to_s方法会将方法接收者转化为string:

《Effective Ruby:改善Ruby程序的48条建议》一第2条:所有对象的值都可能为nil

如你所见,nilclass#to_s返回一个空字符串。使to_s如此之棒的原因是string#

to_s方法只是简单返回self而不做任何转换和复制。如果一个变量是string,那么调用to_s的开销最小。但如果变量期待string而恰好得到nil,to_s能帮你扭转局面。作为例子,假设一个方法期待其参数之一为string,使用to_s,你可以避免参数为nil产生的

问题:

《Effective Ruby:改善Ruby程序的48条建议》一第2条:所有对象的值都可能为nil

有趣的事情还在发生。如你所愿,对几乎所有的内置类(built-in classes)来说都存在一个匹配转换方法。这里有一些适用于nil的最有用的例子。

《Effective Ruby:改善Ruby程序的48条建议》一第2条:所有对象的值都可能为nil

当需要同时考虑多个值时,你可以使用类array提供的优雅的讨巧方式。array#compact方法返回去掉所有nil元素的方法接收者的副本。这在将一组可能为nil的变量组装成string时很常用。比如,如果一个人的名字由f?irst、middle和last组成(其中任何一个都可能为nil),那么你可以用下面的代码组成这个名字:

《Effective Ruby:改善Ruby程序的48条建议》一第2条:所有对象的值都可能为nil

nil对象的嗜好是在你不经意间偷偷溜进正在运行的程序中。无论它来自用户输入、无约束数据库,还是用nil来表示失败的方法,意味着每个变量都可能为nil。

要点回顾

根据ruby的类型系统的运作方式,任何对象都可以为nil。

如果方法接收者是nil,nil?方法返回真值,反之为假。

在适合的时候使用转换方法,如to_s和to_i,可以将nil对象强制转换为你期待的类型。

array#compact方法返回去除所有nil元素的接收者的副本。

继续阅读