《Effective Ruby:改善Ruby程序的48条建议》一第3条:避免使用Ruby中古怪的Perl风格语法

简介:

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

第3条:避免使用Ruby中古怪的Perl风格语法

如果你曾用过Perl语言,那么无疑,你会意识到它对Ruby的影响。Ruby的大部分Perl风格语法都已和Ruby生态系统的其余部分混合得非常完美。不过其他一些则不然,比如坚持使用不必要的分号,或是代码如此杂乱以致为了明白它如何工作,你抓耳挠腮却不得其解。
多年以来,随着Ruby的成熟,有些古怪杂乱的Perl风格语法已经被新的语法替代。再后来,有些Perl的残留语法被弃用或是从Ruby中直接移除。不过,仍然有一些还在,因此你可能会意外地遇到它们。本条可以作为解密那些Perl风格语法的指南,并指引你避免在自己的代码中引入它们。
你在Ruby中最容易遇到的借自Perl的特性是一组特殊的全局变量。事实上,Ruby对于全局变量拥有漂亮的自由命名规范。与局部变量、实例变量,甚至常量不同,Ruby允许你使用所有类型的字符作为变量名。回顾一下,全局变量是以“$”字符开头的,思考下面的代码段:


03054d5d1be1b84cc3f1b385960dcc22efedcac6

这段代码中有两例Perl语法。第一例是类String的“=~”方法。如果匹配到,它会返回字符串被右操作对象(往往是正则表达式)匹配到的位置,否则返回nil。当正则表达式匹配时,会将匹配结果设置到几个全局变量中以供导出。本例中,我使用全局变量$1导出第一个匹配组的内容。这就是有些古怪的地方了。因为这个变量看起来像是全局变量,但实际上并不是。
由“=~”操作符创建出的变量被称为特殊的全局变量。其原因是,它们的作用域仅限于当前进程的当前方法。从本质上说,它们是形如全局变量的局部变量。前例中,在方法extract_error之外,“全局”变量$1的值是nil,即使在使用“=~”操作符之后也是一样。把返回值赋值给$1就好比赋值给一个局部变量。整个情况都很令人费解。不过好消息是这一切都不是必要的。来看以下的替代方法:


6c1c478e77268a1ec00aed4012e83dd812fa4843

使用方法String#match则更符合语言习惯,并且该方法不使用任何由操作符“=~”所产生的特殊的全局变量。这是因为方法match的返回值是一个MatchData对象(在正则表达式匹配之时),它包含所有使用特殊的全局变量时所能取得的所有信息。在extract_error这个方法的版本中,你能看到,可以使用索引方法,索引1取得的字符串与上例中使用$1获得的字符串是一样的。额外获得的特性是,对象MatchData是传统的、普通的局部变量,因此你可以自由命名。(如此例这样在条件语句如if表达式中赋值是很普遍的。也就是说,在条件语句中,当你期望使用“==”时误用为“=”是如此容易。要小心提防这种错误。)
除了利用操作符“=~”赋值以外,还有借鉴于Perl的其他全局变量。最常见的是$:,在Ruby想要查找通过require方法加载的库时,$:表示了一个包含所有库的目录的数组。用于替代使用全局变量$:的方式是,使用其描述性别名:$LOAD_PATH。事实上,这些古怪的全局变量如$;和$/都有更具描述性的别名。但是这也有些问题。不像使用$LOAD_PATH那么简单,你不得不通过加载一个库来访问其他全局变量的
别名:


d248333fd41e8931185f18d7e1dd63e26613f683

一旦库English被加载,你就可以使用更长但更表意的别名去替换那些古怪的全局变量了。想知道这些别名的完整列表,请查阅English模块的文档。
不出意外,最后一个需要注意的Perl风格的语法也和全局变量有关。请看:


4078881fe4e3e5dfa2a6b052e1d469ed974fae49

如果你对这段代码感到疑惑,哈哈,恭喜你,你在一个不错的公司任职。你大概在想print方法到底会打印些什么,以及那段正则表达式会匹配些什么。这段代码起作用是因为一个全局变量$_。
那么,究竟发生了什么呢?该方法开始于readline方法。更具体地说,是Kernel#readline方法。(在第6条中,我们将深入挖掘Ruby如何定义这些方法,在这里,readline来自Kernel模块。)这里的readline和IO类中的readline有些许差别。你可以推断它是从标准输入中读取一行并返回它。其中的奥秘在于,读取时会将那行输入存储在变量$_中。(Kernel#gets也会做相同的事情,不过在读到文件结尾标记时不会抛出异常。)以此类推,如果Kernel#print方法在没有任何参数时被调用,它将打印$_变量存储的内容到标准输出中。
你可以试着猜猜“~”操作符以及正则表达式在做些什么。操作符Regexp#~试图使用右边的正则表达式匹配$_变量的内容。如果匹配成功,返回匹配的位置索引;否则,返回nil。你现在知道,所有这些方法魔法般组合在一起完全是拜全局变量$_所赐。不过为什么Ruby会支持这个呢?
这些方法(包括变量$_)的唯一合理的用法是,在命令行中为了书写简短而使用简单的脚本之时,因此也称为“一行写法”。它允许Ruby和诸如Perl、awk以及sed这些工具竞争。当你书写真正的代码而非脚本时,请避免使用这类隐式使用全局变量$_去读写的方法。这些也包括其他类似的Kernel方法,如chomp、sub和gsub。不过在Ruby近期的版本中,如果运行Ruby解释器时没有使用“-n”或“-p”这些命令行选项的话,它们也不能再被使用了。也就是说,如果没有这些命令行选项,这些方法就如同不存在一般。这是件多好的事情啊。
现在,你看到了这些更加古怪的Perl风格语法是如何影响可读性,并进而影响你的代码的可维护性的。特别是,那些隐藏的全局变量和仅仅看上去像是全局变量的局部变量。我们强烈推荐使用更像是Ruby的方法(String#match与String#=~),以及使用全局变量的更加表意的名称($LOAD_PATH与$:)。
要点回顾
推荐使用String#match替代String#=~。前者将匹配信息以MatchData对象返回,而非几个特殊的全局变量。
使用更长、更表意的全局变量的别名,而非其短的、古怪的名字(比如,用$LOAD_PATH替代$:)。大多数长的名字需要在加载库English之后才能
使用。
避免使用隐式读写全局变量$_的方法(比如,Kernel#print、Regexp#~等)。
相关文章
|
8月前
|
存储 前端开发 JavaScript
前端学 Ruby:熟悉 Ruby 语法
前端学 Ruby:熟悉 Ruby 语法
28 0
|
2月前
|
Ruby
|
2月前
|
Ruby
|
2月前
|
Ruby
|
2月前
|
Ruby
|
10月前
|
缓存 监控 数据库
使用Ruby构建可扩展的Web应用程序
在当今科技驱动的世界中,Web应用程序成为了企业和个人进行业务活动、提供服务和与用户互动的重要方式。而Ruby作为一种简洁、优雅且易于学习的编程语言,已经成为许多开发者的选择。本篇博客将介绍如何使用Ruby构建可扩展的Web应用程序。
83 0
|
Ruby Python
红袖添香,绝代妖娆,Ruby语言基础入门教程之Ruby3基础语法,第一次亲密接触EP01
书接上回,前一篇我们在全平台构建好了Ruby3的开发环境,现在,可以和Ruby3第一次亲密接触了。 Ruby是一门在面向对象层面无所不用其极的解释型编程语言。 我们可以把编写Ruby代码看作是一场行为上的艺术,编码就像跳舞一样,Ruby的每一步都很优雅,几乎没有一步是多余的。
红袖添香,绝代妖娆,Ruby语言基础入门教程之Ruby3基础语法,第一次亲密接触EP01
|
存储 对象存储 Ruby
Ruby Programming | 连载 02 - Ruby 语法基础
Ruby Programming | 连载 02 - Ruby 语法基础