对于RoR(Ruby On Rails)的走红,大牛们的争论够多了,偶等鼠辈不论是再添油加醋还是添砖加瓦,也不会有更多的新意。窃以为这种争论是积极的,因为能引起争论的东西必定有利有弊,偶等鼠辈如果真正的去了解一下被争论的东西,并且从大牛们的辩论中学到些什么,最后从中取其糟粕去其精华(或反之),那肯定是好事。
关于RoR,最早是从去年的一期《程序员》上看到过些许概念,后来看了不少大牛们的争论,浏览了一遍DHH那本著名的"Agile Web Development with Rails",动手做了一点小程序,感觉它的确是个有趣的东西。说它“有趣”而非“有用”是因为对于它的反面意见也是很客观的,比如:
- 毕竟RoR还没有太多真正的成功案例,一说到实例,通常只有Basecamp, 43things,
- 如果web应用程序只提供CRUD功能,那RoR是最佳选择,
- Ruby的性能让人很难恭维,
- 两天之内连发两个补丁来解决安全问题,这肯定不该在成熟的软件框架/产品中出现,
- Rails is 8 times slower than Spring+Hibernate
- 等等
如果没有了解过RoR,看到这些问题,你会不会对它感兴趣?我会。因为一个存在这么多问题的东西居然引起这么多的争论,那它必然有过人之处。
于我个人体会最深的一点是RoR的设计和实现集中体现了一种思想:
“make simple thing easy and make complex possible”。
个人理解这种思想是对80/20理论的一种诠释。在软件开发生命周期中,对80/20理论可以有不同的理解,比如80%的时间花在20%的需求上,80%的用户只需要20%的功能,20%的代码解决了80%的问题,等等。参与软件开发项目时间越长,对这一点的体会越深,而且在实践过程中,我们也会很自然的做一些工作来让简单的事情变的容易,同时让复杂的事情的实现成为可能。而这种思想贯穿整个RoR,也许正是对这种思想的有效实践,使得RoR引起了人们极大的关注。可以说RoR所体现的这种思想比RoR本身更吸引人,Rails几乎成了敏捷web框架的代名词,Java社区的Grails,.NET开源项目Mono Rails和Subsonic,PHP也On Rails,Python的Karrigell,Django等。形式上也许各异,骨子里都是在集中体现这个思想。
下面是对于RoR里体现这一思想的特性的一点体会,见识浅薄,惭愧得很:
- * Ruby,抛开性能的因素和个人对其国籍的一点偏见,这真是一个很好玩的语言:容易的事情在Ruby里做起来都很简单,甚至简单到让人不习惯,比如既然函数/方法都要返回,那为什么一定要写“return”,于是ruby里到处可见“def add(a, b) a+b end”这样的方法定义,初学者会困惑于“为什么即看不到返回类型,也看不到return语句”,然而一旦熟悉,就会非常喜欢这种简洁的表达方式。再比如循环和判断语句可以写成“s = s*s while s < 1000”或“print a if a > 0”,这种“倒装”语句更接近自然语言也让代码变的更简洁清晰。再如“-3.abs”, “(0..9).each do |d| print d end”, “resp, data = Net::HTTP.new('www.google.com', 80).get('/index.html', nil)”等等,这些容易的、常用的任务尽量用简单的方式表达,另一方面ruby对于多数复杂或者高级的需求提供了可能性,一个简单的例子是:象“-3.abs”这种语句的背后体现出的正是ruby对OOP的强大支持(一切都是对象,java和c#里也只能以“Math.Abs(-3)”这种方式来表达)。Ruby有自己的标准库,可以完成作为一种通用语言的大多数功能,比如threading, network, gui, windows互操作等等。而作为一种动态语言,ruby最牛的地方莫过于mixins这种运行时动态织入的特性,不需要接口,不需要多重继承,却能以更简洁的方式实现这些高级的OO特性,这一点是ruby,python,javascript等动态语言受到越来越多关注的一个重要原因。JRuby的作者被sun收买,IronPython和Ruby CLR的作者被MS收买,大概都是因为大公司们很想在自己的开发平台中引入更多的动态语言特性吧。IronPython for ASP.NET就是个很好的例子。总之作为RoR基础的Ruby语言本身就很好的体现了“让容易的事情简单同时让复杂的事情有可能做”这一思想,RoR的很多关键特性,比如ActiveRecord,正是借助了Ruby所体现出的这一理念。
- * CRUD, ActiveRecord,应用程序当然不是CRUD,然而事实上大多数应用程序都需要CRUD,CRUD是一种典型的简单但麻烦的工作。人们在各种开发平台上使用各种开发技术为简化CRUD做了各种努力,比如ADO.NET中的DataAdapter(内置了实现CRUD的command及其builder),比如各种ORM框架,比如ActiveRecord(我不清楚把ActiveRecord看成一种ORM是否合适,毕竟按Martin Fowler的定义,ActiveRecord是一种设计模式)。大家的努力都是为了让CRUD这件容易的事情更简单并且更容易适应数据结构的变化。如果单从易用性来看,使用ActiveRecord做CRUD更简单,每个对象可以有自己的Fetch,FetchByxxx方法,从开发者的角度看这些对象,它们知道如何加载和保存自己,对象自己来维护IsDirty之类的标识,开发者不必关心这个对象应该被insert还是update,对象间的关系也不强制,在需要关系的Model里用belongto和hasmany来指明就好了。其它多数ORM框架,数据访问都需要先加载一个Persistence Manager之类的上帝来指挥对象该怎么跟数据源交互。RoR中ActiveRecord更有特色的地方在于,基于动态语言特性,开发者甚至可以无需定义而直接使用FetchByLastName之类的方法,而不必使用Find("LastName=xxx")这样的语句,简直神乎其技。当然并不是说ActiveRecord就比其它ORM的方式要好,ActiveRecord只是在“用简单的方法做容易的事情”方面做的更好,而在另一个方面,在处理更复杂或困难的数据访问中,RoR中的ActiveRecord也只是做到“把复杂的事情变的可能”而已,比如对transaction的支持、对复杂查询的支持、对跨数据库访问的支持,对cache的支持,对支持继承的数据结构的支持等等。对RoR来说,这些是可能的,但并不容易做,而更完善的ORM框架在这些方面做的更好或更有特色,比如Hibernate, iBATIS,IdeaBlade等的。
Castle有个ActiveRecord的子项目,基于NHibernate实现的,由于.net静态语言的原因,在动态特性的实现上没有RoR中那么灵活,它基于.net中的attribute来标识字段和关系,在.net平台上也很好用(它也是给Castle的Mono Rails提供数据访问服务的)。
另外最近发现了一个开源的.net项目Subsonic ,号称“Zero Code DAL”,它的ActiveRecord实现的非常有趣,它把ActiveRecord的生成过程插在asp.net项目编译时,一旦编译成功,在asp.net中使用对象时就好象它们是动态生成的一样,很爽。这个项目还是一个很好的学习参考,从它的代码能学习到如何实现一个ActiveRecord、一种代码生成的方式以及.net中的很多高级特性。
总结一句废话:RoR中ActiveRecord非常典型的体现了“把容易的事情变的简单”这一思想,而另一方面它也只是做到了“把复杂的事情变为可能”而已,如何应用,要看实际的工作更看重80还是20。
- * Scaffold,N年前,一位前辈曾经力推过他的“软件工厂”的概念,当然以现在的眼光看,他的概念与微软的Software Factory有很大差距,微软的p&p已经推出了几个Software Factory,比如SmartClient SF, WebClient SF,WebService FS,MobileFS等,相信不久的将来还会有WPF based的software factory。这些factory包括核心架构、模式、组件、方法、模板、可重用代码、实现参考等等,目的是为开发者提供一个完整的最佳实践。而那位前辈提出的概念,大致相当于其中的“模板”和“可重用代码”,他总举的一个例子是:“用VB写业务功能,大部分窗体都是雷同的,把数据库的schema读出来,按模板生成vb窗体的代码,就能节省大量的工作,而且可以重用。”这种观点没什么问题,不过有些片面了,或者把软件开发考虑得过分简化了。
不过这说的也正是Scaffold的作用。Scaffold就是盖大楼前要搭的脚手架,字面意思已经很明确了,它大致上实现这样一个过程:根据一个预定义的结构(数据库的schema也好,基于xml的配置信息也好)和一套预先定义好的模板,一次性生成能够完成针对多个实体的的代码集合,如果有特殊需求,再在生成的代码基础上做修改。这样做的好处是80%的工作量是自动完成的,剩下20%的工作在这个基础上完成就好了。如果软件开发中的“工作量”只是指代码行数的话,这是对的。
这种认识当然是片面的,也不会真的有人这样理解软件开发过程,不过这种方式的确是提高开发效率的一个有效办法,而且对于多数业务密集型的应用都是适用的。从另一个角度考虑,可以把Scaffold理解为前面提到的CRUD自动化的进一步扩展,这种扩展是很自然的:既然CRUD的方法都自动生成了,那么为什么不进一步把操纵CRUD的标准UI也生成了呢?RoR做得很好,Scaffold就是一个脚本,运行一个“ruby script/generate scaffold Product”命令就可以把对应"Products"表要的list, new, edit等功能及其所需要的view, action, model甚至unit test代码都生成了。可以想像,大概DHH当初非常偏执于给人一种震撼的效果,即“运行一下这个scaffold命令,再去看浏览器,所有的一切及其他都在了”。第一次试用RoR,这一点的确让人惊叹,甚至RoR都能推断出一个日期类型的字段要显示成可选年月日的组合框。到此为止,RoR一直充分的体现着“把简单的事情变容易”这一理念。另一方面,如果开发者不喜欢scaffold生成的代码,完全可以不用,甚至可以创建自己的scaffold,理论上讲,构建任意复杂的应用都是“可能”的。
scaffold容易吗?我觉得使用它很容易,实现一个合理、完善的scaffold很困难。实现一个scaffold的前提之一是对要实现的代码以及要完成的功能有深入理解和抽象,至少要明确:在要解决的问题域中,什么样的逻辑或功能有共性,可以动态生成,而什么样的东西需要放在生成的代码所依赖的框架里,生成代码是否容易扩展,是否简洁等等。这方面的体会很多,这也是写这个东西的初衷,希望借助于对RoR思想的思考,来考虑如何实现针对多种客户端的scaffold实现,以后再写更多的废话吧。
- * Helpers,Helper是啥,应该没有什么明确的定义,粗俗的讲,helper就是.net里包含了一堆静态方法的类,是sdk里扁平化的函数库,是vb,python和ruby里的module,跟对象的状态没啥关系,谁都能调用,啥时候都能调用。咋来的呢?个人体会:当写了一大堆代码后,假模假式的做做代码重构,发现很多方法太长,把其中相对独立的并且与所在对象状态无关的代码块拿出来单独放在一个private方法里,把方法名起得规范些,更有意义些,当这种函数越来越多,影响代码阅读了,就再建几个类,里面只放这些方法(还有一些常量),再把这些类的命名规范一下,把相关的方法分一下类,方法改成public static的,基本上就可以美其名曰为helper了,常见的命名如DataAccessHelper, EmailHelper, LogHelper等等:) 。从OO的角度看,这种方法顶不咋的。从实用(或者说偷工减料)的角度看,大概谁都这么干过。
那么理论上更合理的办法是什么?我觉得是Provider模式或者Dependency Injection,也就是说对某一组功能(比如Data Access,Membership,Log,Cache),可能有很多种实现方式,比如你可以用文件系统来做log,也可以用数据库,对数据访问你可能访问Sql Server,也可能访问MySql,甚至连GUI都可以是可选的。既然世界充满了可能性,那么用一种固定的方式来实现某种充满可能性的功能就显得缺乏灵活性和可扩展性了。好的办法是定义一组接口,你的应用所需要的功能都在接口里定义好了,如果你需要的功能在接口里没有那怎么办?那就扩充接口呗,反正根据OO设计的开放-关闭原则,只要你的接口设计对于扩展是开放的,对修改是关闭的就成。有了接口就需要有对接口的实现,面向对象或者面向组件的理论里有一点让人难受,就是过分关注了接口(或者说契约)的设计,而矫枉过正地忽略了“实现”的重要性,就好像对接口的实现是组件的事,大部分都是现成的,把接口设计好就行了,而且说的跟真的一样。只有偶等鼠辈在闷头为了实现客户一个个变态的需求的时候才会骂上几句“NND,接口就定义了个Excute(),也不管如何Excute。”。说跑题了,接口设计好了以后,业务系统就可以针对接口来编程了,这种做法的好处马上就体现出来,比如你可以在web 应用中上针对一个IRichTextBox来编程,在没有实现一个很理想的Rich Text Box控件之前,可以简单的把TextArea包在一个user control中,并且让这个user control实现IRichTextBox接口就行了,不会影响其他依赖于它的业务逻辑的开发,也不会因为以后要换成一个Rich的编辑控件而对现有代码大动干戈。另外这种面向接口的设计方式也是另一种流行的方法学TDD(Test Driven Design)的基础,比如说做Unit Test的时候如果不想连接真实的数据库,就向"Unit"里传一个Mock对象好了,Mock对象实现了数据访问接口,就好像它真是数据访问组件一样,骗骗"Unit",反正目的是测试“Unit”的功能而不是为了测试数据库连接。当然这种unit test的方式的前提就是unit是依赖于接口编程的,而接口的“实现”是可以根据需要变化的的,用Martin Fowler的话说叫“注入”。这样“unit”依赖于接口而不是传统的依赖于“实现”,就是所谓的“依赖翻转”了,就有了IoC的概念,又衍生出了“面向方面”(AOP)的概念,而对于依赖的处理是很复杂的事情(比如依赖如何定义,如何注入,依赖的依赖怎么办等等),于是又有了各种各样大同小异的容器来管理依赖的注入和初始化的时机,比如Spring(及其.net版本), Castle的MicroKernal和Winsor,MS的ObjectBuildere等等,偶合从此松散了,世界从此又乱作一团。
说了这么些并不是认为这种依赖接口的设计和编程不好,恰恰相反,我每天的工作都从中受益匪浅,打个比方说:我从前要买书,依赖于书店的位址,书店的环境以及售货员的嘴脸,这就好比我做买书这件事要依赖于书店的“实现”,而互联网时代,当当也好,joyo也好,我不管你的总部在北京还是上海,也不管你如何存放我要的书,书店的网站就是你的接口,我通过接口下订单,你把书发给我就好了,即使你的书店换了老板,或者总部搬了家,都与我无关,这就是松散偶合的好处,在软件开发和现实生活中都一样。
如果说Provider或依赖注入是设计模式,那么Helper更象它们的反模式,RoR强调“约定高于配置”的理念,体现在很多方面,它提供了丰富的Helper而不是提供一个支持依赖注入的框架就是一个例子,比如text_field,password_field这种针对html元素的helper,用起来即方便,功能也强大(RoR没法象asp.net那样实现页面元素和model的双向数据绑定,但通过这些helper来以约定好的格式来生成客户端代码,在服务器端按这种约定好的格式来收集数据,或者说反序列化来自于客户端的页面数据,这也形成了和数据绑定同样的效果,通过scaffold生成的代码中,edit和new模板中使用了大量这种helper)。helper的理念进一步体现了“让简单的事情容易做”这一思想。至于复杂的事情,你可以定义更多的helper来完成,也可以利用ruby的面向对象特性甚至mixin特性来开发一个更完善的架构,都是“可能”的。
窃以为用不用Helper的方式全凭个人喜好。很多时候,有太多的选择还不如没有选择,RoR“约定高于配置”的理念告诉我们:默认只给你提供一种方法,也许不是最好的,但一定是很实用的,你用我们的方法来实现你的需求就好了。我想对80%的开发者的80%的工作来说,这种理念都是合适的。
- Convention Over Configuration,“约定高于配置”,刚刚探讨了一大堆,再简单列举一下个人认为很实用的实例:
- YAML,Ruby并非没有能力操纵XML,那为什么非要另一种标新立异的描述语言来存放配置信息呢?首先RoR弱化配置,和J2EE项目中的海量配置信息相比,RoR几乎没用什么配置信息(不管怎样,数据库连接总还是要配置的),另一方面,YAML也很好,简单明了,易于解析,关键是够用,其实谁说配置非得xml,ini不都挺好的么。
- O/R Mapping,“约定高于配置”的集中体现,绝大多数ORM架构都有至少有一个记录OR映射关系的配置文件,RoR默认就没有,它使用Scaffold生成model,默认情况下就是英文复数的表名对应单数的Model,DB字段名对应Model字段名,表中必须有叫做ID的整形字段作为key等等很直觉的约定。这样开发者就不用为了“可能”存在的灵活性而维护一个大的OR Mapping配置了。这样简单的事情容易了。RoR也允许不按这个规则来做,当然就会很复杂,但实现这种复杂的事情的可能性是存在的。
- MVC框架,RoR的MVC框架清晰,简洁,你要用RoR开发web框架,就一定要按他的方式做,model文件就放在models目录里,controller,view,helper分别放在特定名称的目录里,只要你按这个规则做了,那一切很简单,如果你较真抬杠非不这么放,那么也许能达到目标,但很累。不过在他的地盘上开发,为什么要不按人家的规则做呢,况且人家的目录结构,命名规则以及URL到action的映射都很合理很清晰,难道自己令建一套规则会更好么?
- Ajax,这年头,一个web框架不支持ajax,都不好意思出去跟人打招呼。RoR未能免俗,而且宣称自己是从骨子里支持ajax的。RoR用的ajax框架叫prototype,也许当初人家就是想做个原型吧,所以才叫了这么个名字。很精致的一个ajax框架,即小巧,功能有全,支持JSON数据格式,还有不少behavior的支持,RoR还提供了很多helper让开发者以更方便的方式实现ajax效果。RoR也不强调其它ajax框架,有这一个,好用,够用,就行了。
- Log和mailer,对一个web应用,log和mailer是很常用的,RoR没啰嗦什么,给开发者提供了很好的实现,比如log,就是使用了Ruby标准库里的Logger类,当然你也可以用其它的logger,只要在config/environment.rb中修改一下默认的log配置就好了。
与其说RoR好,不如说贯穿RoR的设计理念对我们是很好的借鉴,说实话,还真没考虑过在实际的项目中使用RoR,但是的确在认真的考虑采用RoR的设计理念,并参考目前已有的一些Rails的实现(如前面提到过的Mono Rails和Subsonic),来设计不同类型的客户端应用。窃以为Rich Client的应用应该更有前途,java的RIA不了解,但基于.net的SmartClient应该是可行的,基于Office的智能客户端也很好。Flex更好一些,基于xml的描述语言加上ActionScript,还有几乎每一个浏览器上都有Flash Player的优势使Flex对客户端的依赖程度很低,很适合以RoR的方式实现快速开发。更有前途的在于WPF,随着Vista的发布和普及,采用WPF作为客户端有着天生的优势,况且以xaml来描述的应用程序简直太适合以RoR的方式快速开发了,control,style,data binding,validation都用可以标签描述,而且可以作为Resource很松散的偶合,System.Windows提供了ObserverableCollection之类天生适合数据绑定的新的数据类型,而且xaml描述的界面可以编译也可以动态加载,可以在桌面运行也可以稍加修改后在浏览器运行,.net3.0上可以用WCF通讯,可以基于WF开发基于工作流的业务,有了WPF/E甚至可以跨平台,等等等等,真是美啊,再加上RoR的理念,给开发带来无穷的想像力。
Vista得慢慢学了,将来得写一个“基于WPF开发可定制表单的一点想法”。
呵呵,胡言乱语的都不知道想说啥了,收键盘。