Ruby
Ruby 是一种成熟的、现代的、纯粹的面向对象编程语言。它的语法简洁且一致,使得 Ruby 既易于阅读和学习,又灵活且富有表现力。如果您有 API 臃肿的语言背景,您会对 Ruby 小巧而强大的核心 API 感到惊讶。Ruby 与底层操作系统紧密集成,并且非常容易扩展,这使其成为一种强大而通用的编程语言。
大胆的断言?让我们揭示这些声明背后的真相。为了演示,我包含了一个简单的 Ruby 脚本,该脚本清除临时目录中早于给定天数的文件。该应用程序让我可以演示基本的 Ruby 语法和该语言的一些更重要的特性。整个脚本包含在清单 1 中 [可在 ftp.linuxjournal.com/pub/lj/listings/issue95/4834.tgz 获取]。它的调用方式是
./purge.rb [tmp_dir] [max_file_age_in_days]
其中 age 决定了文件在从临时目录中清除之前需要存在多久。您可以将对此脚本的调用添加到您的 crontab 中。
Ruby 是一种面向对象的语言,它提供对象内数据和方法的封装,允许从一个类继承到另一个类,并支持多态性。一切事物,包括字符串和整数等原始数据类型,都表示为对象。甚至常量和类也表示为对象。这使得 Ruby 成为一种纯粹的面向对象语言。这里唯一的例外是控制结构,少数表达式,如 for、if、while 等。这些不是对象。
如清单 2 所示 [可在 ftp.linuxjournal.com/pub/lj/listings/issue95/4834.tgz 获取],delete_older 方法包含顶层程序逻辑:遍历给定目录以检查要删除的文件。
对于那些习惯于 Java 或 C++ 等类型语言的人来说,方法参数缺少类型声明可能看起来很奇怪。但 Ruby 是动态类型的。也就是说,变量没有类型,但它所引用的对象有类型,因此声明中缺少类型。动态类型优于类继承的对象组合。方法调用中传递的对象的类型不受控制,减轻了对复杂继承层次结构的担忧,因为我们不再依赖多态性将对象传递到方法中。这导致更简单、更可重用的代码。
Ruby 的方法声明对于 Python 程序员来说应该很熟悉。这两种语言声明方法的方式几乎相同,包括可选参数的使用。调用方法时可以省略可选参数。省略参数与使用可选参数的默认值调用方法相同。
Ruby 的方法声明也缺少返回值。由于该语言是动态类型的,因此无需声明返回类型。除非使用 return 语句显式指定返回对象,否则将返回最后评估的表达式,就像在 Lisp 中一样。
通过向目标对象发送消息来调用方法。这是 Smalltalk 的方式。target.message(参数列表) 消息传递表示法对于所有面向对象的程序员来说都应该很熟悉。向对象发送消息会调用目标对象上的相应方法。所有对象间的通信都由消息传递处理。
Ruby 使用两种方法概念进行操作:类方法和简单地称为方法或实例方法的方法。实例方法在实例化的类(更常见地称为对象)上调用。类方法在未实例化的类上调用,类似于 Java 和 C++ 中的静态方法。由于类方法在未实例化的类上调用,因此可以将其视为库方法。它不操作对象的成员变量。
考虑以下调用脚本的代码,它处理命令行参数
path = ARGV.shift or raise "Missing path to delete" age = ARGV.shift or raise "Missing age in days"
ARGV 是一个数组对象,其中包含脚本调用中的命令行选项。“shift”调用返回并删除数组的第一个元素。Ruby 有一个高级数组类。数组是动态的;它可以自行调整大小。它是一个对象,因此您不必担心内存问题或超出其末尾。该类还包含允许您按索引、按元素以及像堆栈、集合或队列一样处理数组的方法。数组可以反转和排序。对于表格查找,请使用 Hash 类。
清单 1 中的以下行显示了 Ruby 的数组是多么优雅
Dir.entries(full_name) - ['.', '..']).empty?
Dir.entries(full_name) 返回一个数组,其中包含目录中的所有文件。然后使用 - 运算符从目录列表中减去数组 ['.', '..']。然后我们可以通过在目录列表上调用 isEmpty? 来查看目录是否为空。如果数组为空,即 isEmpty? 返回 true,则目录中没有其他文件。
处理完调用参数后,就该调用 delete_older 了
delete_older(path, age_in_seconds) rescue puts "Error: #$!"
执行期间可能会发生错误。例如,如果使用不存在目录的路径调用脚本,则当 Ruby 第一次在 Dir 类上调用方法时,将发生错误。上面的代码不仅调用了 delete_older,还处理了执行期间可能发生的错误。这里的关键是 rescue 表达式。当发生错误时,Ruby 解释器会将错误打包到异常对象中。此对象向上回溯调用堆栈,直到到达某些显式声明它知道如何处理这种特定类型的异常的代码。从未捕获的异常会传播到调用堆栈,最终导致程序异常终止;堆栈跟踪将打印到 stderr。这与 shell 脚本和 C 返回错误代码相反,从而减少了嵌套语句,减少了意大利面条式逻辑,并简化了更好的错误处理。
在与 rescue 表达式结合使用时包含 ensure 语句,可确保无论发生什么情况,代码块都会运行。将此与编写您自己的异常、使您自己的代码抛出异常(使用容器程序清单中显示的 raise 表达式)以及通过运行一些代码并重试导致失败的代码来实际从异常中恢复的可能性相结合,您将拥有我使用过的最简洁的错误处理机制之一。
让我们回到 delete_older,看看 Ruby 的一些更高级的功能(请参阅清单 2 [可在 ftp.linuxjournal.com/pub/lj/listings/issue95/4834.tgz 获取])。第二行看到在 Dir 类上调用了 “foreach”;foreach 是迭代器设计模式的实现。如果您正在进行面向对象编程,但尚未阅读四人帮的开创性著作 设计模式,您最好赶快去买一本。迭代器不是核心 Ruby 中实现的唯一模式。还实现了单例、发布者/订阅者、访问者和委托模式。如果需要,也可以简单地实现其他模式,但列出的模式随 Ruby 一起提供。
foreach 迭代目录中的文件。调用 Dir 的 foreach 之后是一个代码块,其开始和结束非常类似于常规 Java 或 C++ 代码块。花括号中包含的代码称为块,它类似于方法中的方法。块永远不会在遇到时执行。每当 foreach 方法从目录中读取单个文件时,它都会将控制权交给块。执行块内的代码,控制权返回给 foreach,后者从目录中读取新文件,重复该过程,直到没有更多文件要迭代为止。
与 Java 或 C++ 中必须编写辅助类才能使迭代器工作不同,Ruby 包含 yield 表达式,这使得可以将迭代器实现为方法。这是该语言的表现力和灵活性的一个主要示例。Ruby 让您专注于完成工作,而不是编写脚手架来实现它。
如前所述,通过以 target.message 形式向目标对象发送消息来调用方法。只有类本地的方法可以在不指定目标的情况下调用。我的脚本调用了 “puts”,它是内核的方法,没有指定任何目标。当 puts 不是对象的本地方法且未指定目标时,解释器如何知道我正在调用哪个方法?
这就是 mix-in 的魔力。Mix-in 基本上允许您将其他地方实现的方法混合到类中,而无需使用继承(有关 mix-in 的更多信息,请参阅文章 “使用 Python 的 Mix-in”,Linux Journal,2001 年 4 月)。Mix-in 不是一个新想法,Ruby 也不是唯一支持它的语言。但它绝对是使 Ruby 具有良好而简洁的语法的特性之一。
我永远无法希望在本文中处理 Ruby 的所有功能。相反,我会将您推荐给 David Thomas 和 Andrew Hunt 的书 Programming Ruby,以获取有关模块、别名、命名空间、反射、动态方法调用、系统钩子、程序分发和网络等问题的更多详细信息。值得一提的是,Ruby 还支持与 Perl 一样好的正则表达式,并支持 CGI,此外还拥有自己的 Apache 模块 mod_ruby。
Ruby 又是另一种脚本语言吗?不,它不是。它是一些更重要的东西,一些来自日本开源社区的新鲜而令人兴奋的东西。Ruby 是程序员最好的朋友。虽然 Ruby 被呈现为一种脚本语言,但它已被证明同样适用于大型项目。它包含一些令人兴奋的功能,其他替代语言才刚刚开始实现。因此,Ruby 非常值得一看。
特别感谢我的技术审阅人:approximity.com 的 Armin Roehrl,感谢他审阅了草稿并指导编辑了最终版本。pragmaticprogrammer.com 的 David Thomas,感谢他大幅改进了原始示例脚本并审阅了草稿。Kent Dahl 和 Sean Chittenden 审阅了草稿。最后但并非最不重要的是,Python 大师 Magnus Lie Hetland,感谢他提供的宝贵帮助。
