澳洲东海岸-梦幻之旅

回到墨尔本,从上周六出发,历经澳洲东海岸的一周梦幻之旅结束了。

先是繁华都市悉尼的二日暴走。

悉尼全景

悉尼海港

Bondi Beach

之后飞临冲浪天堂黄金海岸,他们是来自世界各地的冲浪爱好者,也包括我们。

冲浪者天堂

Kirra海滩一望无际的白色沙滩

白色沙滩

途中又顺路游览了恬静怡人的布里斯班

Brisbane

最后一站来到潜水天堂凯恩斯,见识了神奇的澳洲土著和热带雨林的奇观

库兰达土著

热带雨林

以及最最最梦幻的海底世界

甲板上暴晒的美女

潜水

海底世界

澳洲这片土地真是充满了惊喜。

Rails系统性能优化之路

这篇文章讲述的是我们在一个Rails on Jruby系统的性能优化之路上披荆斩棘的故事。

优化之前

在开始性能优化之前,有几点必须明确:

1. 性能优化的对象:并不是所有页面都需要优化,而且首先应该选择那些访问率最高、性能瓶颈最大的页面来进行优化。

2. 性能优化的目标:性能优化必须有一个具体的目标,即要达到的响应时间和吞吐量。有了目标,我们就知道目前离目标的距离,需要优化的力度;同时,也知道在何时停止优化。

3. 伴随优化的测量:没有测量的性能优化很可能让你缘木求鱼。

优化之路

下面,分别从多个方面看一下性能优化中的一些实践。

缓存

缓存是性能优化的一大利器。Rails本身对缓存机制有很好的支持。

客户端缓存

一般而言,对于客户端缓存的原则是:对动态html页面不作任何缓存,永久缓存任何其它类型的文件。

Rails很好地支持了这个原则。比如:

stylesheet_link_tag(”application”)

生成的页面元素是:

<link href=”/stylesheets/application.css?1232285206″ media=”screen” rel=”stylesheet” type=”text/css”/>

大家都注意到在css文件后面有个时间戳,这其实是一个小计策:当文件发生改变的时候,时间戳也发生了改变;而客户端会认为这是一个新链接,就会重新获取文件。有了这种机制的支持,我们可以很放心地在客户端对静态文件进行永久缓存,而不用担心过期问题。

服务端缓存

Rails提供了三种服务器端缓存方式:page cache,action cache和fragment cache。对于动态页面,fragment缓存使用的机会会比较多。

让我们看一个例子:一个产品页面片段,片段上面的部分信息对于所有用户都是一致的,但另一部分对于不同权限用户是不一致的。在admin登录时,能看到a链接,b按钮和c复选框;在一般用户登录时,能看到b按钮和c复选框;未登录用户只能看到c复选框。

一种很简便的缓存策略是根据用户标志进行缓存,即对用户p1缓存,对p2也进行缓存,那么我们就可以根据用户的标识以及产品的标志来定位缓存。

但是,这种缓存策略存在一个问题,即缓存数量太大:同一个产品片段存在n份不同的拷贝,n = admin用户的数量 + 一般用户的数量 + 1 (未登录用户)。而且,对于同一种权限的用户来说,他们对同一个产品的缓存是一样的。

消除这种重复的策略是根据用户的类型来对缓存进行分类,那么对于一个产品而言,它只会有3份并且不重复的缓存,3 = 1 (admin权限) + 1 (一般权限) + 1 (未登录用户)。

这 可能是一个比较简单的例子,比较容易想到。但在一些很复杂的情况下,可能就会迷惑。其实原则就是:消除重复,根据片段的根本特征差别来对缓存进行分类。但 这里引起了另外一个问题:如果根本特征的区别需要对很多数据进行大量的计算,那么缓存就失去了它的意义。所以,要把握好权衡。缓存,最重要的是为了减少数 据的重复获取,减少重复计算。

另外,缓存的清理策略是至关重要的:何时清除缓存,以及如何定位失效缓存。对于要求实时性的页面,可以使用 rails提供的sweeper机制来进行缓存清理。但sweeper有个不方便的地方是需要对相关的action都逐一进行声明。我们的系统巧妙地利用 了rails的observer:在observer检测到数据的更新时对相关缓存进行清理。

计算结果缓存

在一个request的生命周期之内,有些数据不会改变,或者我们不关心改变,则可以通过对结果缓存以避免重复计算。

def length
@length ||= end – start
end

内存

来看个例子:我们要在一个页面中显示一百个产品的信息,产品信息从一个信息搜索平台获取。下面的代码是从搜索结果数据集创建产品对象:

records.map { |record| Product.new record }

但这里有个问题,Product是一个ActiveRecord类,但页面显示结果并不需要这么“重量级”的对象。取而代之以轻量级的对象会减少内存的消耗,并提升速度。

数据库

性能的瓶颈往往不在语言级别,而在IO上。对数据库的优化是重中之重。

在数据库的优化上有一些耳熟能详的方法,比如:

  • 增加适当的索引以加快查询。有些很好的工具可以帮助我们找到性能的瓶颈,比如分析execution plan。
  • 缩小transaction的粒度以减少锁的见时间。
  • 避免多次创建transaction的开销(如下代码)
Product.transaction do
search_results.each do |search_result|
Product.create(
search_result)
end
end


大型查询

对于像报表类的大型查询,要绕过ActiveRecord,而直接使用数据库驱动。同时,对于多表连接的查询,可以考虑几种方法来优化:

  • 引入中间表,让另外一个进程定时把查询结果插入中间表。但这会牺牲一定的实时性。
  • 避免重复结果的获取,减少结果集。这往往出现在一对多表之间,连接会导致“一”这边出现多条重复结果。可以考虑把一个查询分拆成多个查询。
  • 使用存储过程或者functions。当然,这失去的是业务逻辑的透明性和灵活性。

Rails的finder

接下来,探讨一下如何正确使用Rails的finder。Rails的finder是很容易误用或者不合理使用的地方,往往有很多性能问题因此而起。

在使用finder时,要搞清楚几个问题:

  • 是否需要结果集中的所有数据?
  • 是否需要预先加载?

表的扫描是邪恶的,很吃性能。一定要减少获取的结果集,用:select指定需要的字段。配合以创建正确的数据库索引,并在索引上挂载其它需要的字段,避免表的扫描。

正确使用预先加载可以避免n+1查询:

Company.all(:include => :products:conditions => “company.kind = ‘toy’”)

产生的sql查询是:

SELECTFROM companies WHERE kind = ‘toy’

SELECTFROM products WHERE products.company_id IN (12, 423, 431…)

但错误使用预先加载是个很危险的事情,它可能不会影响结果的正确性,但会引起很严重的性能问题:

Company.all(:include => :products:conditions => “products.id IS NOT NULL AND products.weight > 10″)

其实写这个查询的人的目的是为了找出拥有products,并且products的weight大于10的company。但这个语句导致的sql查询是性能低下的:

SELECT companies.id AS t0_r0, …., products.id as t1_r0, … FROM companies
LEFT
OUTER JOIN products ON products.company_id = companies.id
WHERE products.id IS NOT NULL AND products.weight > 10

这个sql查询有两个问题:

  • 结果集中的products信息是不需要的
  • LEFT OUTER JOIN的性能劣于INNER JOIN

我们可以使用如下的语句来避免这两个问题:

Company.all(:joins=> “INNER JOIN products ON products.company_id = companies.id, :conditions => products.weight > 10)

它生成的sql是:

SELECT companies.* FROM companies
INNER
JOIN products ON products.company_id = companies.id
WHERE products.weight > 10

这个查询的效率会高很多。所以,正确使用Rails的finder是至关重要的。

页面

View的helper方法生成html元素,比如:

link_to “My Company”, company_path(@company)

# => <a href=”/companies/1″>My Company</a>

过度使用helper方法会引起性能问题。比如上面这个方法,每次都会调用link_to和company_path — 从routes中解析path。

但注意,只有在过度使用的时候,才需要考虑是否可以减少helper方法的使用来提升性能。同时,也可以用一些工具替代ERB来提升性能,比如erubis:它通过预处理避免每次调用的重复开销。

多线程

版本2.2之后,Rails终于是线程安全的了,意味着我们可以开启多线程模式,这绝对是一个巨大的进步。

非线程安全的Rails,无法有效利用共享资源。在以前的版本中,假如我们的应用部署在mongrel上,那么对于n个并发请求,就需要n份rails,n份application,n个数据库链接等等。几乎所有的东西都无法共享。

多 线程模式,对于Rails on JRuby带来的改变尤其巨大,因为jruby的线程是native thread(相对于ruby的green thread)。比如我们的系统只开启了一个应用实例来处理所有的请求(当然,当瓶颈出现在一些共享资源上时,可以考虑增加实例)。

还是用数字来说话吧:Rails on JRuby的内存使用是原先非线程安全时的1/n (n是并发请求的数量),是Rails on Ruby的线程安全模式的1/m(m是cpu的数量)。

那么,开启多线程模式要注意什么?如果你的应用中有类变量的使用,请注意它们是否会在并发下出现问题。

服务器

服务器的配置优化对性能的影响很大。我们的系统以war包的形式部署在tomcat上,主要的配置优化在于对JVM的内存分配上。可以从这几个方面看:

xms — 初始内存大小是否合适?如果你的系统在一开始的时候就需要较大的内存分配,就可以设置一个合理的xms值。
xmx — 最大内存大小是否合适?如果太小,会导致OutOfMomery,会导致持续的GC;反之,则会导致一次GC时间过长,在这段时间内,系统的性能将会受到影响(当然,这跟GC的算法有关)。

性能优化的几个原则

更近

数据库,应用服务器,web服务器以及客户端,这是信息传输的一条链(当然,有些系统的链会更长,更复杂)。那么,减少响应时间的一个原则就是:让数据离客户端更近。

一个最能体现更近原则的优化就是客户端缓存–客户端是距离用户最近的地方。

更加细节的一些例子,比如sql server的nonclustered index现在可以把非键的数据挂载在索引的叶子节点上,这样就不需要再去表上扫描获取这些数据。这也是更近原则的一个体现。

更快

更加快速的响应,需要更加快速的计算。前文中提到的加速页面元素的生成,就属于更快原则。

这方面的实践包括有算法优化,数据库索引,使用更加高效的方法,使用正则表达式匹配等等。

举个Rails中的例子:使用Model.find_by_*方法是很低效的,因为它需要调用method_missing来动态生成方法。而Model.find_by_sql方法的效率高很多。

更少

传输过多的数据,进行过多的操作,可能都会影响性能。

减少数据的传输量有很多例子:压缩静态文件,以减少服务器和客户端之间的传输量;避免在action中滥用实例变量,以减少实例变量在action和view之间的传输;正确合理使用finder,以减少从数据库中获取的数据量等等。

减少操作次数的例子有:合理运用预先加载,减少查询次数;减少transaction的不必要重复创建等等

其它还有:缩小transaction的粒度;缩小并行运算锁的粒度等等。

平衡

在追求更近、更快、更少的时候,要注意平衡。

比如给JVM分配一个合理大小的内存,而不是过大或者过小;不要滥用数据库索引,要考虑是否会影响插入数据的速度;平衡一个运算在空间和时间上的消耗;保持性能在长时间内的平稳等等。

性能优化是一个不断实践、不断调优的过程。比如前文中提到的服务器端缓存,最后并没有被采用,因为我们发现相比而言缓存命中的开销反而更大。

小结

在经过一系列的优化之后,我们的系统很好地满足了客户对性能的要求。下面是几点总结:

  1. 大多数性能问题都出在IO上,IO应是关注的重点。
  2. 性能优化是一个实践的过程,空讲理论是没有意义的。
  3. 出现性能问题,先不要怪罪于平台、语言、框架,大多数性能问题都产生于错误或者不合理的实现。
  4. 性能优化过程并不一定需要贯穿整个项目的始终,但一定要时刻保持对性能问题的关注:从刚开始的架构设计,到项目开发中的代码编写、重构等等,性能都应该是关注的一个方面。
————————————————————————————————————–
此文是三个月之前的旧文了,刚发表于2010年1月刊的《程序员》杂志。

读书时间:《软件开发沉思录》

软件开发沉思录》,参与了翻译,出版也很久了。但上个礼拜才拾起来,今天才把整本书读完。

13个来自不同职位、不同角色的ThoughtWorks员工成就了这样一本书–可以说,非常精彩。

看完这本书,第一个感觉就是公司知识和文化的传承做得充分到位。因为读很多章节,都会联想到自己在曾经或者现在项目中遇到的问题和解决方案,它们是何其的相似。第十二章-一键发布,我们在曾经的项目里面就完美地实现了。第十四章-实用主义的性能测试,我甚至很想厚着脸皮说:我们实际上早就已经超越了。

第四章-语言的盛景,让我们看到一个百花齐放的时代,这不,Google刚刚发布了它自己的语言Go。同时,随着并发的需求越来越强烈,也让我下定在今年学习一门函数式语言的决心。

公司老板在第二章里面就开宗明义的说到:敏捷过程的价值,就在于减少从“提出业务需求”直到“软件上线来满足业务需求”这两个端点之间所需的时间与成本。但这不妨碍也不冲突一个软件开发人员拥有一些艺术上的追求。在整本书中,我最钟爱的就是第六章–对象健身操。这个章节的内容并不算新,它们都可以在前人的一些书籍中找到,比如《重构》、《设计模式》等。但作者巧妙得总结出一套简单的实践和易记但深刻的经验来支持程序员写出优质的代码。如果说《重构》、《设计模式》等是基石,那么对象健身操就是一套在基石之上的简单的工具集合。

总的来说,这本书,值回票价!

不要在习惯中死去

忍受是可怕的,因为它会让你慢慢习惯,直至死去却不自知.

当进入一个新团队的时候,我发现有太多的事情不能忍受.

我不能忍受机器环境的不一致:   团队已经为此付出太多无谓的时间.测试在这台机器上跑过了,却在另外一台机器上失败了.

我不能忍受团队的低效:  有太多可以自动化的命令,团队却还在坚持使用手动操作.

我不能忍受没有code diff环节的工作: 因为我突然感到心虚了,因为我对团队中其他人所做的事情毫无所知.Code diff是一项比站立会议有意义多的行为.

我不能忍受回顾会议没有action list: 大家提出了各种担忧,但没有一项得以执行.

我不能忍受设计中的坏味道: 当我提出进行重构的时候,团队却以没有时间来推脱.我相信,设计中过多的坏味道就是当初的”没有时间”所导致的.要等到无可挽回的时候再来补救么?

我不能忍受以所谓的隔离为由让所有的测试都成为mock测试: 因为这已经成为一个公认的弊病,但团队还是听之任知.

我不能忍受团队的保守: 对于新建议缺乏开放的态度.

我更不能忍受团队的短视: 屡屡以工期为由放弃了可以带来长远好处的实践.

两个礼拜之前,当我跟团队提出这些建议的时候.他们的第一反应让我惊讶,因为他们觉得”我们做得挺好的,没有改进的必要”.当我再试图慢慢渗透时,他们终于承认了这些实践的必要性,但他们还是以”没有时间或者我们都知道就可以了”的目的推脱.

两个礼拜之后,当我对其中一些曾经的坚持慢慢妥协之后.有种感觉突然上来了:”就是这样的“.

我知道,我的坏味道已经出来了.我们团队仍旧没有实行code diff的实践;我也为项目贡献了越来越多的mock测试……

忍受到习惯了.这时候,离死期也不远了.

当第一辆车出现破窗户却对它置之不理的时候,越来越多的破窗户就会出现了.

大道理谁都懂,但没有行动等于什么都没有.

胡凯的敏捷之形和熊节的知易行难已经在述说着相似的故事,发生在不同地方的版本.

这同时又让我反思,在曾经我也已经很习惯的团队里面,是不是也有这些我们未曾发觉,或曾经发觉但最后已经习惯的坏味道? 我们曾经是否以开放的态度接受了别人的建议,还是以自我感觉”挺好的”而不加考虑? 绝对有!

团队及时彻底的反思,以及开放的态度至关重要.这是避免让坏味道成为习惯的根本.那如何做到反思彻底,态度开放? 先让下一次回顾会议成为一次真正的回顾会议!

Vim

It’s very important for you to manage a text editor if you are an programmer, no matter if it’s Vim or Emacs. It cannot cost you longer than one week, but you can benefit from it for a life!vim

Lonely Planet Office

Lonely Planet office座落在墨尔本西南市郊,办公室的房子颇有点798红墙工厂的风格.

Lonely Planet Office1

隔着Maribyrnong river跟市区遥遥相望. 江边上那些集装箱很多都来自中国.

Lonely Planet Office2

Lonely Planet Office3

办公室里面很fancy啊,很有一个大家庭的感觉.

Lonely Planet Office4

Lonely Planet Office7

走进他们的办公室,书库,琳琅满目ing~~  Ellen很慷慨地让我自己随便拿,不过我只是很收敛地拿了两本.

Lonely Planet Office5

Lonely Planet Office6

Ellen带着我在办公室上上下下转了一圈,见了很多人.有些是已经共事很久,但从来没见面的朋友.见到之后很有种惺惺相惜的感觉. Ellen还特地带我去见了负责亚洲事务的女士,在她的桌上发现这本熟悉的书.

《ThoughtWorks文集》中译本序

这本《 ThoughtWorks文集 》中译本面世之际,也正值“敏捷中国2009大会”召开在即。两者可谓相得益彰。

从 2004年进入中国,ThoughtWorks见证和参与了中国敏捷社区的发展历程:从五年前的筚路蓝缕,到如今的欣欣向荣。更令人欣慰的是,在原则、价 值观等“大问题”上,敏捷的实践者们已经基本达成共识,社区的话题更加趋于关注实践──这意味着敏捷社区正在步入成熟,将用他们的知识和技能为各自效力的 企业创造更大的价值。

我们在这个时候把《ThoughtWorks文集》翻译出版,是希望为社区的发展再尽绵薄之力。作为敏捷方法 的积极推动者,ThoughtWorks从多年、多个行业的实践中积累了丰富的经验。本书收录的13篇文章涵盖了编程技术、项目管理、持续集成、测试等方 面内容,将带领读者了解ThoughtWorks在软件生命周期各个环节所推荐的工作方式。

比较难得的是,这本《文集》不仅由 ThoughtWorks员工撰写,也由ThoughtWorks员工翻译。译者们或是与文章作者素有私交,或是在文章所论述的领域有所专擅,这也使得翻 译的质量更有保障。感谢这些译者在工作之余的辛勤翻译,才使这本《文集》如期付梓。他们是:韩锴,胡振波,金明,李剑,乔梁,熊节,徐昊,张晓庆,郑晔。

一本薄薄的《文集》当然不可能解决所有问题,我们更希望它能够收到抛砖引玉的效果。希望ThoughtWorks的经验心得能对国内的敏捷实践者们有所启发,帮助他们做出更多创新,创造更大价值。最后,希望你阅读愉快。

郭晓
总经理,ThoughtWorks中国公司

澳洲游记 – 墨尔本之city

两个礼拜之前,从浦东机场飞往墨尔本.上了飞机之后感到无比的窒息,狭小的座位,十几个小时的飞行,漫漫长夜如何度过…

右边坐着一位光头留学生,跟他使劲地打听这边的生活.后来就迷迷糊糊得睡着了.过了一会儿,光头把我叫醒.原来吃早餐了.一看表,四点多(北京时间).吃完早餐,又打了会儿盹.感觉飞机已经在下降.光头望着窗外,叹了一口气:又到了这个万恶的地方.

想比于浦东机场,墨尔本的机场完全就不起眼.据说有个叫Ozzi老兄来机场接我.刚开始看到这个名字,我一直以为是个澳洲土著.没想到人家还是个正经白人.

星期天早上,我想应该不会堵车吧.车子从机场到住所,也就不过20分钟距离.一路畅通.其实应该说,路上根本就没什么车.用英国兄弟Mike的话讲,这个城市好empty.

在车上Ozzi拿着地图跟我比划:住所在哪里,公司在哪里,中国城在哪里.看着地图中四四方方的一小块,脑中和北京地图比较了一下.怯怯地问道:明天早上我怎么去公司,你来接我么? Ozzi回道: You can’t be lost. 后来我才知道,那四四方方的一小块,是两平方公里的CBD,也就是光头跟我说的所谓city.站在住所的阳台上往外看,一公里之外高楼就突然消失了,远处就是树林和隐约所见的房子.

这两个礼拜,一直困在city,没有出走半步.因为要在这边呆很长时间,所以也就没有那么急迫的动力出去玩.不过今天第一次出去跑步,就像发现新大陆一样,原来在我住所旁边就是Victoria Harbor. 海风微拂,春和景明. 港口上停满了私人游艇. 不知道为什么,周末竟然这么少人出海.

在city里面闲逛,没有太多异乡的感觉.因为身边经常会出现中国人的面孔,听到中国人用汉语谈话.在超市买东西,抬头一看,发现都是中国人的身影.到了中国城,这种感觉就更甚了.

同事多次邀请我去pub玩,刚开始我都谢绝了.因为实在不怎么喜欢pub,最重要是不喜欢和老外在pub玩.他们讲什么笑话,我基本上只能看着他们笑我也跟着笑.不过有一次我终于还是跟着他们去了,在pub里面喝了一种叫做zombie的东西.刚开始听不懂这个单词,于是他们就跟我解释这个东西在中国应该是被禁止的.我在想我靠不会让我喝什么带有毒品之类的东西吧.然后他们又向我解释这个东西就是dead people stand up.我终于明白是僵尸.不过还好只是个名称而已,做法很奇怪,用的杯子很奇怪,加了各种原料.味道却还不错.

从pub出来也就是晚上十点,但路上已经人影稀疏,出奇的安静.墨尔本多次被评为世界上最适宜人居住的城市.之前一直没怎么感觉,现在终于明白原来人越少的地方就是越适宜人居住的地方.

光头一直跟我说这是个万恶的地方,但我发现这里的人民生活挺安康悠闲的.社会已经发展到一个稳定的状态,资源丰富,而且能让多数人共享.不同职业间没有太大收入的区别,所以大多数人可以根据自己的兴趣选择职业.这里的物价相对于收入水平而言其实很低.家庭年收入的四倍就能在CBD买一个公寓.当然光头的压力比较大,因为拿着人民币在这里花.

虽然呆的时间不久,但隐隐有种感觉.就是对于生活的理解悄然发生了一些变化.下班就是下班,周末就是周末,像我这样周末还在写博客的人是很少的.而中国人,特别是城市人,永远都生活在奔波中.无论是富人还是穷人.对于他们来说,没有下班,没有休息.

到这边之后还没拍过照片,以后补上~~

Rails之美

本文发表于《程序员》杂志2009年10月刊。可能由于编辑的工作繁忙,发表的不是此最终版本。杂志发表版本中有些不恰当表述,对此造成的困扰,深表歉意。
 
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. 对于任何语言,要写出简洁优美高效的代码,都需要精雕细琢。
3. 随着语言的进步,开发效率真正的瓶颈越来越多地体现在业务逻辑上,而不是代码的编写上,特别是对于复杂系统而言。

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

无关敏捷,关乎责任

JJG在《The Elements of User Experience》特别强调,要让每一个人参与到网站设计中:高层管理人员,市场人员,销售人员,等等。不过这里,我想他忽略了一个很重要的群体,就是开发团队

The Elements of User Experience》把用户体验分为五个要素: Strategy, Scope, Structure, Skeleton, Surface。

其中最根本的是strategy,因为它是用户的需求和网站的目标

在我们的开发过程中,拿到一个story并不意味着开发的开始,而往往很多时候我们会花很多时间论证这个story的价值所在。开发团队经常会向客户提很多问题,探究这个story的起源和目的;开发团队经常和客户一起讨论甚至争论一个story的功能或者设计,因为随着开发的深入,对项目的了解,我们有义务告诉客户我们所想,帮助客户找到真正所需。

讨论的结果可能证明开发团队是错的,也可能证明客户是错的。但双方都在讨论中对story的价值越加清晰。

因为通过争论,客户会发现

  • 其实这才是我们真正想要的:经过向公司相关人员咨询,发现这果然是更好的方案。
  • 原来可以通过这种更简单的方式得到我们想要的,得到用户所需要的。
  • 应该丢弃这个功能,这样做是错的,这样的设计不仅对我们未来的业务发展没有好处,而且还可能成为一个束缚。
  • ……

开发团队:

  • 的确客户是对的,我们在实现的东西是有价值的。
  • 又一次不仅帮助了客户找到了真正的价值,也避免了让自己花很多时间做一个用户不会喜欢的功能。
  • ……

印象特别深刻的是在项目结束之后,客户的BA诚挚地对我们说:谢谢团队的每一个人,谢谢你们不停地问问题

后面的那句话,我想,是他们意外得到的。所以在感谢的时候特别地提了一下。

开发团队保证正确实现客户所要的,就够了么?不,开发团队要保证正确实现客户真正想要的

这里无关敏捷,这里关乎责任

The Elements of User Experience

Next Page »