Rails之美

文/胡振波

 

作为一个有着一年多Rails 开发经验的开发人员,笔者希望通过这篇文章跟读者交流和探讨一下 Rails 开发的心得体会,体验一下 Rails 之美。

Rails开发之美,我总结的有这样几点:简洁、透明、自由、开放、轻灵、丰富和优美。可能你已经感觉到,这些词汇大多展现的是感性的一面。没错, Rails 开发的每一天都是那么 “ 畅快 ” ,畅快背后其实就是这些生动的感触。笔者希望从这些简单的感触出发,结合实际的例子,来展示 Rails 真实的美。 

Rails之美 

简洁

可能很多人在推荐别人使用Rails 的时候,都会列举一个理由:简洁。的确,简洁是促使很多人开始学习和使用 Rails 的原因。那到底什么是简洁?简洁可能代表少,简洁可能代表没有重复,简洁当然也代表复杂的对立面。

Rails是基于 ruby 语言的。动态语言带来的好处之一是代码量的急剧减少。有一个鲜活的例子,有一次跟客户进行 pair ,把曾经用 Java 实现的一个 900 多行的类,缩减到了 100 行。客户很是惊讶。当然,纯粹量的减少可能并不代表什么,但至少带来了清晰和易读这两个对代码来说非常重要的特性。

因为动态语言的良好支持,Rails 框架使重复的配置工作减少到了极致。比如在 Java 世界的大量OR Mapping 配置文件,在 Rails 里面不再需要。虽然现在 Java 世界的配置量也在不断地精简,但还是占据了一定的工作量。重复工作的减少,亦即工作效率的提升。

作为WEB 开发领域的 DSL , Rails 提供的各种机制在各个层面极大地简化了开发的工作量和难度。比如ActionView 提供的 FormHelper ,简化了页面上 form 的生成;比如 ActiveRecord 提供的 Association,简化了模型之间关联的维护。

举个association 的例子,来看一下 Rails 的简洁之处。下面这两个模型是一对多的关系:一个 lightbox有很多 images 。 

class Lightbox < ActiveRecord::Base    
end    
   
class Image < ActiveRecord::Base    
end 


要删除一个lightbox ,以及它的所有 images ,需要这样写: 

@images = Image.find_by_lightbox_id(@lightbox.id)    
@images.each do |image|    
  image.destroy    
end    
   
@lightbox.destroy 

  

接下来,让我们给它们声明正确的关联关系: 

class Lightbox < ActiveRecord::Base    
  has_many :images, :dependent => :destroy    
end    
   
class Image < ActiveRecord::Base    
  belongs_to :lightbox 
end


则删除操作就变得简单了,且在语义和逻辑上更加明确和清晰: 
@lightbox.destroy


DSL的一个目的是使某个领域的开发变得更加具体、简单和清晰。 Rails 框架是从一个现实项目中提炼出来的,这同时也证明了一句话:好的框架都不是凭空想象出来的。 
Rails还有很多其它方面可以体现它的简单。简单,就是美。 

透明  

项目开发过程中,让我觉得很痛快的一件事情是:基本上不需要借助任何外部的文档。 
因为Rails 本身是透明的,这首先是动态语言提供的好处。当需要了解任何一个方法的功能或者实现时,只需要跳到那个方法查看源代码即可。

同时,对大多数方法,Rails 都提供了详尽的文档以及具体的示例。

开放的源代码,以及详尽的注释,让开发人员得以在一个“ 透明的环境 ” 上进行开发。开发中可以彻底地了解所用工具的习性,这不可谓不是一件痛快的事情。 

自由

对一个问题,Rails 往往都提供了多种解决方案。我们可以根据问题的场景,自由地选择合适的方案。

比如对于页面中form 的生成,我们可以选择使用 form_tag 方法。但当这个 form 跟对象关联的时候,更好的选择是使用 form_for 方法。结合 text_field 等 helper 方法,使页面上的元素跟对象的属性更加紧密地结合。

下面再举一个association 的例子,来看看 Rails 如何表现自由的精神。声明多对多关系时,一般是这样的: 

class Teacher   
  has_and_belongs_to_many :students   
end   
   
class Student   
  has_and_belongs_to_many :teachers   
end  


但有时我们需要利用中间表,并让它映射到一个模型。那么,可以这样来声明模型之间多对多的关联: 

class Teacher   
  has_many :relations   
  has_many :students, :through => :relations   
end   
   
class Relation   
  belongs_to :teacher   
  belongs_to :student   
end   
   
class Student   
  has_many :relations   
  has_many :teachers, :through => :relations   
end  


选择的多样化带来的是自由。但根据需要和场合选择正确的方案,更加重要。 

开放

Rails所体现的一点极其重要的精神是:开放。因为 Rails 从来不限制你去做任何事情。 
有个项目是建立在一个遗留数据库上,并且大多数数据库表结构和遗留数据因为有些原因不能更改。但问题是表结构并不满足Rails 的约定,下面列举一些问题和解决办法来窥探一下 Rails 的开放精神所在。

问题1 :单数表名

根据Rails 的约定,表名都是复数形式的。比如一个 User 模型,对应的表名是 users 。而遗留数据库上的表名是单数形式: user 。

解决方案

在environment.rb 文件的配置初始化里,关闭默认的复数表名配置: 
config.active_record.pluralize_table_names = false


问题2 : type 字段不代表单表继承

根据Rails 的约定, type 字段是单表继承的保留字段。当从数据库读取数据并实例化成对象时,它会根据type 字段的内容来寻找相应的子类型。但这里 type 字段并不代表单表继承。

解决方案

在模型里面声明另一个字段代表单表继承,比如: 
set_inheritance_column :clazz


问题3 : type 字段的值不满足单表继承的约定

又出现另一个问题,type 字段用来表示单表继承,但是它的值并不满足单表继承的约定。根据 Rails 的约定, type 的值应该是类名。比如 ShoppingCart 继承自 ImageCollection ,那么 type 的值应该是"ShoppingCart" 。但在遗留数据库里,使用的是 "shopping_cart" 。

解决方案

这里涉及到两个问题,一是在存储一个对象时要设置正确的type 值,二是实例化成对象时,需要根据type 值找到正确的子类型。通过查看源代码,发现计算 type 值和子类型的分别是 ActiveRecord 上的sti_name 和 computer_type 方法。大家都应该想到了解决方法,就是覆盖这两个方法。对于ShoppingCart 类,解决方案可以这样: 

def sti_name 
  super.underscore.upcase 
end 

def compute_type(type_name) 
  super(type_name.downcase.camelize) 
end


通过上面的例子,我们已经了解了Rails 的开放。 Rails 有很多约定,但不代表强制。而且 Rails 的开放不仅限于此,比如你可以打开任何一个类,往里面添加方法(当然,这是 Ruby 给予的权力)。举个例子,比如我们可以通过打开 NilClass ,来实现 Null Object 模式(在有些情况下这种做法比较极端) : 

NilClass.class_eval do  
  def your_method 
     ... 
  end 
end


有些语言在天性上对程序员防备多于信任,他们总觉得赋予程序员过多的权力,会容易带来破坏。但其实防止破坏靠的应该是程序员的修炼和自我约束。语言,应该以一种更加开放的态度赋予程序员更多的权利和自由。 

轻灵

很多人都觉得Rails 是一个庞然大物。但其实 Rails 并不庞大, DHH 在迷思系列里面也解释过。而且,Rails 可以轻松剔除任一可选组件。比如要去除 ActionController 的 benchmark 组件,只要注释掉include ActionController::Benchmarking ,并删除相应的文件即可。

在这背后,其实有一个神奇的方法,叫做alias_method_chain 。这个方法非常有用,它的设计理念很好地支持了 Rails 轻灵的特性,因为它让 Rails 的各个可选组件都只是很 “ 轻巧 ” 地挂载在上面。继续用Benchmarking 这个例子来了解一下它。

Benchmarking的功能是度量 action 的性能,并把结果输出到日志。这其实是对 perform_action 方法的增强。从 Benchmarking 源代码中可以看到如下的代码: 
alias_method_chain :perform_action, :benchmark


以及perform_action_with_benchmark 方法的实现。

其实,alias_method_chain 跟下面的实现是等价的: 
alias_method :perform_action_without_benchmark, :perform_action  # 为原来的方法建立别名 
alias_method :perform_action, :perform_action_with_benchmark      # 重定向原来的方法名到功能增强之后的方法


这种方式其实就是AOP 的工作方式: ActionController::Base 声明了 perform_action 方法,但它对benchmarking 一无所知,只要把 Benchmarking 模块包含进去,就获得了 benchmark 的功能。 Rails 通过这种方式实现了低耦合,我们可以轻松地选择去除非必要的所有可选组件。 Rails 并非庞大,它是轻灵的,但我们需要了解它。


丰富

发展到今天 , Ruby和 Rails 社区已经非常的活跃和强大。千万开源爱好者在不停地贡献着各种各样的Rails 插件和 Ruby 库。

比如认证系统插件:restful_authentication ,分页插件: will_paginate 等等,这些插件的出现帮助我们节省了很多工作。并且,这些插件的实现都非常的优美,并不断地在优化和演进。 
强大的社区支持,丰富的插件,让Rails 开发变得更加容易。 

优美

Rails的优美体现在很多地方,比如它本身就是一个 REST 风格的 WEB 架构。

但REST 就代表着优美么?这不足以让人信服,还是举个例子吧。

假定有一个InvoicesController ,现在需要生成一个 invoice 的 pdf 。我们可能会想到给 controller 添加一个叫做 download_pdf 的 action : 

def download_pdf  
  invoice = Invoice.find(params[:id])  
  send_data(generate_pdf(invoice), :filename => "#{invoice.no}.pdf", :type => "application/pdf")  
end


但从另一个角度看,其实pdf 只是 invoice 这个资源的一种表现形式。而展现一个资源,更适合让show action 来做。用 REST 风格实现 invoide 的 pdf 下载: 

def show  
  @invoice = Invoice.find(params[:id])  
  respond_to do |format|  
    format.html  
    format.pdf { render :pdf => generate_pdf(@invoice) }  
  end  
end 


用正确的方式做正确的事情,就是优美的体现。 

反思

介绍了这么多Rails 开发的优点,肯定有人不禁要提问: Rails 开发难道就没有欠缺的地方么?

笔者也一样,在开发过程中不停地反思。比如为Rails 创造最大声誉的 “ 快速开发 ” ,就值得谨慎看待。用 Rails 搭建的系统在代码量上确实少了很多,但纯粹用代码量来衡量开发效率是不准确的。有几点思考:

1.  当今程序员不再是纯文本编辑时代,强大的IDE 极大地提高了程序员的开发效率。但作为一种动态语言, Ruby 目前还很难享受到这种好处。

2.  2. 对于任何语言,要写出简洁优美高效的代码,都需要精雕细琢。

3.  3. 随着语言的进步,开发效率真正的瓶颈越来越多地体现在业务逻辑上,而不是代码的编写上,特别是对于复杂系统而言。

4.  从上文列举的那些例子中我们可以看到,Rails 很美,但只有在正确使用它时才会很美。初学者可能会因为经验的缺乏落入一个又一个陷阱。不可否认, Rails 的性能问题一直是大家担心的。但系统的性能问题真的是 Rails 本身引起的么?看似优美的代码,背后是否做着一些 “ 丑陋 ” 的事情?希望能在下篇讲述性能优化故事的文章中跟大家再次分享和探讨这些问题。


 

作者简介:

胡振波, Thought Works 公司咨询师。毕业于浙江大学计算机学院, 3 年工作经验。喜爱阅读、编辑、旅游和户外。

 

(本文来自《程序员》杂志0910期,更多精彩内容敬请关注0910期杂志)

《程序员》杂志官方网站:http://www.programmer.com.cn/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值