Ruby on Rails 好像一直处于争论的风口浪尖。大多数争论的核心是其所宣称的令人惊异的生产力。 Bruce Tate 已经开始理解 Rails 并不是一个更好的工具,而是一个不同类型的工具。本文研究了使 Rails 在某个领域如此高效率的折衷和设计决策。然后思索了应该在 Java™ 社区获得更多关注的受 Rails 启发的思想。
Ruby on Rails(也叫做 Rails)是一个针对支持数据库的 Internet 应用程序的 Ruby 框架。我现在已经将 Rails 用于两个不同的应用程序并涉及了另外两个关联的程序。为了即将完成的新书 Java to Ruby,我已经采访了很多 Rails 开发人员(那些在该框架上既成功也失败过的人)、框架的创始人和 Rails 书籍的旗舰之作 Agile Web Development with Rails(参见 Resources)的主要作者。我开始理解为什么 Ruby on Rails 架构如此成功。
炒作和怀疑论
在 Java 社区关于 Rails 的争论已经相当激烈并且在将来一段时间没有停止的迹象。Rails 的支持者称赞它的惊人的效率,与 Java 开发相比效率大约是 10:1。作为 Java 程序员,您下意识的反应是不相信任何宣传过高的效率,因为您可能以前听到过这些,然而实际让您很失望。Java 提倡者日益坚持 Ruby on Rails 是一个玩具,不能伸缩,会生成坏的代码,并且只能开发简单的应用程序。但是随着对 Rails 的赞扬不断出现(通常来自可信的来源),一项更加谨慎的任务是理解 Rails 能做好什么事情,并把它的思想带回到 Java 平台。在本文中,将探究为 Rails 带来巨大效率的核心特性(即秘笈)。
Rails 基本原理
Ruby on Rails 框架不是大家所想的典型的应用程序开发框架。Rails 的创始人 David Heinemeier Hansson 通常把该框架称为固执己见的软件,并且他喜欢打破长期存在的约定。David 做出了非常有哲理性的决策并在整个框架中严格遵循这些决策。遍布于 Rails 内的核心观点有:
·无缝集成:Rails 聪明地利用了 Ruby 语言的最好特性。它扩展了 Ruby,但您很难说出 Ruby 在哪里结束,Rails 从哪里开始。您也可以看到 Active Record(Rails 的持久引擎)和模型-视图-控制器(MVC)框架之间进行了很好的集成。例如,您可以编写三行代码,创建一个表,然后立即为该模型生成用户界面。
·约定优于配置:为保持良好的灵活性,Java 框架保持了大量普遍的配置文件。Rails 不采用这种策略。它为方法、类、表和列采用普通的项目目录结构和简单普通的命名约定,以推断哪些已配置在 Java 应用程序中。结果是,Rails 应用程序只需要对应 Java 应用程序的一小部分配置代码,一般是十分之一或更多。
·低重复:不要重复自己(Don't Repeat Yourself,DRY)是 Rails 社区的一个常见术语。Rails 框架委员会使用通常看起来像是 Ruby 语言的扩展的方法来把重复的任务抽象出来。正如您在本系列的 第三篇文章 中看到的,Rails 的元编程策略使每行代码都执行更多的任务。
·即时反馈:使用 Rails,对于您所做的大多数工作都会给出即时反馈。编写一行代码并保存后,在加载下一个 Web 页面时将激活您所做的更改。更新了您的数据库以后,迁移可以向您即时显示更改。
专注于某个领域
反对其宣称的过高生产率的争论通常类似于这样:如果获得了一把好的锤子,就很难找到另外一把生产率达到两倍的锤子,更不用说把生产率提高 5 到 10 倍了,因为锤子已经发展演变几千年了。但是把 Ruby on Rails 与各种通用目的的 Java 框架相比较的人是不得要领的。通过从根本上改变工具的本质可以在某些方面提高 10 倍的生产率。现在专业的制造者使用钉子枪能够在用锤子钉入一颗钉子的时间内钉入很多钉子。像钉子枪一样,Rails 也是有专门用途的。它是一个专门编写来用于单个领域的框架:新的支持数据库的 Web 应用程序。
我猜想现今构建的应用程序有一半是支持数据库且基于 Web 的应用程序。所以 Rails 是明确针对某领域的产品,但是这个领域很大也很重要。专攻此领域使 Rails 具有巨大的优势,引起巨大轰动。通过专注于此领域的项目,Rails 的设计者可以选择一些其他框架不能或者不应该采用的捷径。这种专门化往往为简单性而失去灵活性。
新的支持数据库的应用程序建议打包方法优于映射方法。正如您在本系列的 第一篇文章 中看到的,Rails 工具采用数据模型中的约定。Rails 应用程序需要 Java 应用程序中创建的一小部分模型代码。如果特别为 Rails 应用程序创建模式,此原则能工作得很好。如果试图把遗留的模式塞入 Rails 中,将变得不太平滑。
基于 Web 的应用程序允许一组相似的优化。当您知道一个应用程序是基于 Web 的,您就能知道应用程序的大体结构和可能需要的主要组件。因为 Rails 关注的是基于 Web 的应用程序,所以在 Rails 中增强了以下功能:
·模型-视图-控制器:Rails 的 MVC 框架(称为 Action Pack)为基于 Web 的访问进行了定制并且实现了著名的被称为 Model 2(参见 参考资料)的设计策略。Rails 版本已经优化了控制器和视图之间的集成(该集成能够使配置文件最小化)并且自动使控制器实例变量可供视图使用。
·项目目录结构:所有 Rails 应用程序都具有相同的项目结构,其中的目录用于存储应用程序代码、数据库配置、公共的静态文件,以及用于管理 Web 服务器和进行基于 Web 的功能测试的脚本。
·架构:通过提供用于生成应用程序组件(这些组件都符合普通架构目标,比如页面级和片段级缓存;两层设计;用于测试、开发和生产的环境)的开箱即用脚本,Rails 框架简化了架构。
·工具:Rails 工具专门用于 Web。日志支持、breakpointer、剖析器(profiler)和测试框架都针对基于 Web 的应用程序进行了修剪并针对两层操作而被启用。
但是钉子枪永远不会取代锤子,我们却愚蠢地希望能完全取代。锤子总能做一些钉子枪不能做的事情。Rails 将永远不会成为用于企业集成、对象关系映射或全堆栈 Web 服务的工具。您可以对 Rails 所做的最好期望是,它是能很好满足它所针对领域的专门工具。
开发人员实践
当您开始透过表面深入研究下去时,您开始了解 Rails 开发人员实践是如此的完全不同。快速的反馈周期、每次的交互控制和约定优于配置,这些都增强了在 Java 框架中不常用的那些方面的开发人员实践。
反馈周期
影响开发人员生产率的最重要因素之一是总体反馈周期。反馈周期是从改变代码到在屏幕上看到执行应用程序的结果所用的时间。在 Rails 中,能够在编码时得到即时的反馈。当您对 Ruby 代码做出更改时,该功能十分显著。可以立即加载一个浏览器页面来查看更改以后的结果。因为在开发期间不需要编译或部署,我倾向于在重新加载浏览器或执行测试用例之前只对编程做微小的更改。几乎每个开始使用 Rails 的 Java 开发人员都以较小的程序块进行编码。
您可能认为对开发人员实践友好的快速反馈周期不支持生产环境。毕竟,频繁地重新加载类能够获得快速反馈周期,但是会使生产应用程序变得很慢。但是 Rails 通过为部署和开发提供不同的环境,避免了这个问题。在开发环境中以应用程序的性能为代价强制频繁地重新加载类,而生产环境则把类的重新加载减少到最低限度,以开发人员的快速反馈周期为代价,为最终用户提供快速的体验。
交互性
Ruby 的交互式体验也有助于 Rails。您可能认为在没有完整的 IDE 的情况下调试 Rails 应用程序将是一个痛苦的过程。实际却不是这样。Rails 提供两种简化调试的功能。其中之一是 breakpointer,它允许您向源代码添加 breakpoint 关键字。
为理解 breakpointer 的运行过程,可创建一个简单的 Rails 应用程序,生成一个控制器,启动服务器,并启动 breakpointer。确保您有权使用 breakpointer 窗口,因为当 Ruby 遇到断点时您将使用它。使用 Windows 时,命令序列如下:
>rails sample
>cd sample
>ruby script/generate controller samples
>start ruby script\server
>start ruby script\breakpointer
如果在 UNIX® 或 Mac OS X 中运行,请确保服务器在一个单独的进程中启动。
把以下代码键入或粘贴到 app/controllers/samples_controller.rb 文件中:
class SamplesController < ApplicationController
def index
breakpoint
@session[:message] = "hi, mom"
render_text "Showing index"
end
def show
render_text @session[:message]
end
end
通过加载页面 localhost:3000/samples 和 localhost:3000/samples/show 来测试代码。
当 Rails 执行到断点时,应用程序暂停。breakpointer 窗口用具有控制器当前状态的环境打开一个 Ruby 解释器。然后可以执行 Ruby 命令来查询会话的状态、执行方法和查询变量:
> puts @session[:message]
-> hi, mom
这种密切联系并没有给您一个完整的调试器,但是您确实能获得 Java 调试器不能为您带来的功能,包括访问完整的解释器和能够执行应用程序的方法。
能够简化调试的第二个功能是 Active Record 控制台。在本系列的 第一篇文章中,您已经看到 Rails 也附带一个脚本,能够让您在交互式 Ruby 解释器窗口处理持久对象。我经常想让我的 Java 应用程序具有这种功能。您可以编写一个持久模型,通过该模型更改数据库,然后运行一些数据库查询来看一下它们对系统的影响。要是能够在类似的设置中查询 Hibernate 对象就太好了。
约定优于配置
约定优于配置也会使新的 Rails 开发人员能够立即上手,因为控制器和模型代码特别简洁。 您在 第一篇文章中 看到,依靠 Rails 环境,可以从一些非常瘦的类获得相当高级的行为 —— 通过采用 Rails 命名约定和由 Rails 推断应用程序的连接点而不是直接配置它们。回顾一下,具有很多属性且与部门(department)具有一对多关系的 Person 对象可能类似于下面这样:
class Person < Active Record::Base
belongs_to :department
end
不需要任何配置,因为 Rails 根据命名约定推断表 (people) 的名称、对象标识符和主键 (id) 的名称、相关的表 (departments) 的名称、外键 (department_id) 的名称、外部类 (department.rb) 的名称。无论对于编写、阅读还是维护来说,代码都保持简单、轻巧和非常赏心悦目。目的直接而清楚。
Java 开发人员能学到什么?
我不推荐用 Java 语言构建一个更好的 Rails。相反,Java 开发人员应该从 Rails 框架学习一些教训,并试图构建或增强 Java 框架以完成下面的任务:
·允许热部署,这将缩短开发反馈周期或支持允许热部署的框架。在 Java 端此优先权应该比现在高得多。
·使用较少的 XML 和更多的约定。约定与配置并不是井水不犯河水,因为可以使用约定来指定明确的默认值,使用配置来覆盖约定。像 Rails 那样使用这种方法,您可以两全其美:具有较少重复的简洁代码而不会失去灵活性。
·为了在调试过程中浏览 Java 类,合并更多的脚本语言,包括 BeanShell(参见 参考资料)。
·为任务选用正确的工具。不必仅仅因为需要持久性就借助于 Hibernate 或仅仅因为需要 Web 应用程序就借助于 Struts。
通过合并其他编程语言的最好特性,您可以不必重复 Rails,但肯定可以改善 Java 体验。