Pier Cawley 撰文探讨了他在一篇介绍延迟初始化属性的博客文章中发现的潜在问题。出现问题的代码如下:
def content<br></br> @content ||= []<br></br>end
这段代码的目的是为了支持类的延迟初始化属性。在这个例子当中,除非@content
这个实例变量已经初始化完毕,否则在它的访问器方法content
方法被调用的时候,它就会被初始化。||=
这个操作符意思是“如果左边的变量值为nil
,将它的值赋为右边表达式,否则仅返回左边的变量值。”
然而,Piers 指出,对于某些值来说,这样做是会出现问题的,因为 Ruby 处理布尔值和nil
的方式比较特殊。我们来看看下面这样一个例子:
a = false<br></br>a ||= "Ruby"
这样的代码结果是怎样的呢?由于a
已经在第一行被初始化,第二行不应产生任何效果。然而,在代码执行之后,我们会发现a
现在的值为"Ruby",而不是false
。
在熟记 Ruby 中编写nil
检查通用方式之后,问题就变得非常显而易见:
if name<br></br> puts name.capitalize<br></br>end
在 Ruby 中,nil
被解释成布尔值false
,因此if
子句中的代码只有在name
的值不等于nil
的时候才能运行。
尽管在通常意义上这不会成为一个问题,但是在延迟初始化属性的代码中,如果付给属性的合法值是nil
或者false
的时候,这就会成为一个问题。在这种情况下,对属性进行访问之后,属性值就会被重设成缺省值。
当然,这是一个边界情况,但是这样的问题会导致人们花很长时间进行调试,来试图找出到底为什么某些方法有些时候会被重设而另外一些则不会。
Piers为这段代码给出了一个条理更为清晰的代码:
def content<br></br> unless instance_variable_defined? :@content<br></br> @content = []<br></br> end<br></br> return @content<br></br>end
这样,代码只会在变量还没有被定义的时候才会初始化变量。
通过这个小例子,我们可以把错误归咎于 Ruby 及其部分语言特性——但 _ 哪一类 _ 程序员会把错误归咎于工具而不是他们自身,这已经是众所周知的事实了。尽管 Ruby 代码的简洁性非常有用,但还是有一些情况下使用更加明确表达意图的表达式会更安全一些。在这个例子中,||=
并非正确的解决方案,相反初始化代码应当检查变量是否已经被定义。
亲爱的读者,您在以前是否也被这样的问题敲中脑门呢?Ruby 是否存在哪些你希望避免的语言特性,以预防上述难于发现的问题呢?
评论