程序思考2: 关于“框架”

来源:互联网 发布:网络平板液晶电视机 编辑:程序博客网 时间:2024/03/29 16:24

我做Java编程大概有四年了,但我仍然处在从Java初级程序员向中级程序员过渡的阶段。 Java 包含的东西实在太多,三大体系 J2ME,J2SE,J2EE 每个都包含了大量的内容,身为一个 Java 程序员其实挺悲哀的,不仅要不停地学习那么多基础知识,还要应付层出不穷的框架。不光要学理论,还要注重实践。想想原来搞 Perl, PHP 的时候,哪来的那么多知识点。虽然我的Java水平不高,但我始终对要学的东西保持自己的看法,对于技术我总是抱着怀疑一切的态度,有时候甚至很固执,就算我是错的,我也要自己撞了南墙来体验。我的观点是:"好话左右不了我,我会自己判断的。大家都说好,我可能偏不喜欢"。

我曾经在编程技术上做过四次选择:
1:选择了 Velocity ,放弃了 JSP.
2: 选择了 Ruby ,放弃了 Python.
3: 选择了 Hivemind, 放弃了 Spring.
4: 选择了 Intellij, 放弃了 Eclipse.

四年前第一次接触 Java WEB 编程的时候,我没有选择学习 JSP ,而是选择了 Apache 的 Velocity 框架。当时的 freemarker 也是相同类型的模板引擎,它甚至可以在模板中使用 JSP 的Taglib,我仍然选择了 Velocity ,因为我觉得视图层并不需要那么复杂。 Velocity 框架很简单,模板语言只负责简单的显示逻辑处理,速度更快,没有 JSP 第一次编译的速度延迟,而且它是一个通用的模板引擎,不光用于 Web 开发,如今它还被用在了 Intellij 中做为代码生成的工具。Freemarker 在模板中使用 Jsp Tag 的功能在我看来是个累赘,使得本来简单的东西添加了复杂性,所以我没有选择它。JSP 从一开始设计就是错误的,它没有及时避免出现失控的情形(页面中充斥 Java 代码,这就类似于 C++ 没有从一开始避免程序员写 C 那样的过程代码一样。而且 Taglib 也不是一个好的扩展显示逻辑的方式。

我在接触 Java 前曾经使用过一段 Zope 和 Python ,即便是在使用了 Java 两年后,我仍在研究 Zope 和 Python 。 使用 Python 这样的脚本语言主要是为了编写一些Java代码生成工具。如果用 Java 来写这种工具,无论编写还是发布都显得不那么完善。脚本语言最擅长是处理文本字符,文件,它们通常内置正则表达式功能,这些都成了编写Code generator 的强有力的工具。 Zope 建立 Web 站点的能力是有目共睹的,相对 Java 来说,它的开发效率更好,虽然执行效率较低,但是满足大多数中小规模网站的制作。接触了 Ruby 后,我逐渐离开了 Python。 原因是 Ruby  在编程中给我了一种从未有过的快乐感。我曾经认为 Python 的缩进语法是它的优势,因为这种强制性使得代码的可读性大大的增强了,即便是多年之后再看自己写的代码,或者是别人写的代码,你还是很容易读懂。但是 Python 语言本身的一些弱点,使得你感觉很不舒服,它的面向对象不彻底,只是具备一些 OO 的特点,使得在使用它的时候有点四不象的感觉。如果你用过 Ruby 这种感觉会更加强烈。不过这种选择取决于你是否是一个“面向对象编程”的强烈爱好者,否则 Python 长期积累的现成的成熟代码库会使得你很容易的选择它的。我想对我个人编程影响最大的一本书,当属 Dave Thomas 的那本 "Programming Ruby"的电子版了。通过它我才得以系统地学习了 Ruby ,并把我以前用 Python 写的脚本程序全部替换为 Ruby 脚本。直到现在,文件操作和数据库备份移植的脚本文件我都采用 Ruby ,而这些操作如果用 Java 来写,复杂性可想而知 。所以我一直认为一个程序员至少应该掌握一种脚本语言,并非为了学习脚本语言而学习它,主要是为了让自己的编程工作更轻松一点。至于后来放弃 Zope 而选择 ROR ,就是再自然不过的事情了。

Spring 的流行其实应该算是 Java 世界反对 Sun EJB 技术的一个必然结果,就算不是 Rod Johnson 推出 Spring 框架,大家也必然会采用另外一个人推出一个基于 POJO 的 IOC 容器框架的。其实在那个时候推出了很多 IOC 容器框架,比如 Pico Container, 还有那生不逢时的 Avalon,这些框架都或多或少有些问题。Pico 只是个简单的 IOC 容器,功能相对较弱。Avalon 不仅仅是个 IOC 容器,它更是一个组件框架。但是它是 type-1 型的框架,也就是说它是基于接口的服务,它的缺点是你必须实现一个特定的接口,这就造成了对特定框架的依赖性。早期的容器采用这种方式有其历史原因,后来推出的 IOC 容器不是采用 type-2(Setter)设置属性的方式来设置依赖关系,就是采用 type-3 (constructor) 构造函数的方式来设置依赖关系。Spring 和 Pico 就是如此,虽然两者都同时支持 type-2 和 Type-3 ,但是 Spring 更多使用 Setter 设置依赖关系,而 Pico 习惯采用构造函数。现在很多人都夸大 type-1 型这种接口依赖性的缺点,认为这是过时的框架(事实上 Avalon 框架最终被 Apache 组织放弃了)。 其实我并不这么看,我们公司使用  Avalon , Cocoon 框架很多年,对于一个多年使用 Avalon 框架的用户, 对过去代码的依赖性可能使得它不会简单地抛弃它,Avalon 组件式开发方式有其优势,在部署的时候有极大的好处,这点我在后面谈到 Hivemind 的时候详细再说。必须继承某个框架的特定接口,会使得将本框架代码移植到新框架的时候出现依赖性问题,但是如果你不存在更换框架的可能性的话,使用这种 Type-1 型框架就没有什么太大的缺点了。人们总是喜欢设置假想的未来预期,比如尽量少用数据库存储过程,因为这样产生数据库移植的困难,事实上真正企业级的项目很少会更换数据库层的,这点Rod Johnson 在他那本名著《Expert One-on-One J2EE Design And Development 》的第一章就做了阐述。所以对 Type-1 型框架的恐惧也是没道理的。 如果你已经在使用这样的框架,那么就坚持继续使用它,它的成熟性可能会给你带来更大的开发益处,但是如果你是个新手,那么最好还是采用 2 和 3 型的框架,能避免依赖性的时候还是应该尽量去做。

Spring 现在可以被看作是一个 FullStack 的全功能性框架,它不象 Struts 那样仅仅是个简单的 MVC 视图层框架,它包含了对 J2ee 开发各个方面的支持。在接触  Spring 框架之前,我就通过阅读 Rod Johnson 的 Expert One-on-One J2EE Design And Development 获益非浅,书中的各种真知灼见对任何一个开发者都有很大的启发,即便他不采用 Spring 来开发自己的应用。现在看来 ,Spring 在 Java 语言层次上已经做到了尽可能的简化和正确的决策。随着对它的了解日益加深,我发现它存在一些问题。大家都说 Spring 的XML配置文件太多了,其实我并不这么认为。看看 Hivemind ,Avalon 这些流行框架,哪个 XML都不少,这是 Java 框架的通病。 Spring 有一个优秀的 IOC 容器,它容纳的是 POJO ,但是它并没有发展成为一个组件框架。Rod Johnson 说"面向对象比某些技术本身更重要",Spring 也是这么做的,使用它你可以容易的面向接口编程,但是面向对象编程跟面向组件编程是两个阶段,面向对象主要是在开发阶段,而面向组件编程,更关注于部署 Deploy 阶段的工作。 一个面向组件的框架要考虑更多的动态部署的问题。 Spring 的问题出在部署上,如果你用过 Avalon 和 Hivemind, 你会觉得它们那种通过替换 Jar 包来更新功能的方式真的很爽,而这种特点在 Spring 中却看不到。 在 Spring 编程中,你会看到清晰的分层:表现层代码,应用层代码,数据持久层代码,它们各司其职,靠的是XML 配置文件来描述各层代码之间的依赖关系。所有这些代码和 XML 配置文件是放在一起的,Spring 应用的部署就是一种 "All or Nothing "的局面,更新一小段代码, 你就要更新整个应用的部署.如果采用 Avalon 或 Hivemind 这样的组件框架, 优势就明显了. 使用这样的组件框架来开发应用, 通常将程序员分类两类: 组件或服务开发者(系统程序员), 应用程序员. 系统程序员负责开发组件或者(服务), 供应用程序员调用. 举个例子, 如果要开发一个网页计数器的应用, 我可以将计数器的功能先定义接口 Counter,然后提供默认实现, 如果你采用文件来记录计数器的数值,可以提供一个 CouterFileImpl 的实现类, 如果采用数据库来记录数值,那么可以提供一个 CounterDBImpl 的实现类. 这种服务的概念,将原来的应用开发三层的概念(表现层,应用层,数据持久层)简化为两层(应用层,服务层), 原来的表现层对应应用层,由应用程序员开发, 而原来的应用层和数据持久层对应服务层,由系统程序员开发. 现在流行的各种表现层框架虽然是按照 MVC 模式设计的, 但是大多只是实现了 VC (视图和控制器),而对于M(模型)并没有提供什么支持. 以前这些框架多是推荐采用 Session Bean 来实现模型(M), 但是随着Spring 等POJO IOC 框架的流行, 模型的实现可以采用 Spring POJO , 和Avalon, Hivemind 服务来替代实现. 应用程序员可以采用任何流行的表现层框架来获取这些服务完成显示的任务. Spring 的问题出现在这里: Spring不是组件框架,沿袭原有的三层的概念, 是以 XML 来组织所有三层代码. 如果要改任何一层的代码,就牵扯到整个代码同时更新和部署. 其实这个问题还是可以通过 Maven 将不同包编译为 Jar 来解决, 但这不如组件框架直接提供这样的服务好. 如果采用 Hivemind ,可以将服务接口和对应服务实现编译为 Jar 包, 接口可以定义在一个Jar 包中, 不同的实现也可以定义在不同的 Jar 包中, 当然接口和所有的实现也可以都放在一个 Jar 包中. 这样如果后台实现变化了(文本记录改为数据库记录),那么系统程序员只要提供一个新的Jar(可以只更新Jar中的配置,修改默认实现的指向)给应用程序员, 这个修改部署就是以Jar包为最小单位.它的简单性是 Spring 提供不了的. 如果你看过 Howard Lewis Ship (Tapestry 和 Hivemind 框架的创始人)的日记, 就会知道他曾经在多年前阐述过这个问题, 并就此跟 Spring 框架的创始人 Rod Johnson 在多个研讨会上交流过,无奈 Rod 有自己的观点, 没有接受 Howard 的意见, 所以才有了后来 Hivemind 框架的问世. Spring 鼓励程序员采用面向对象思想,面向接口编程, 但是没有针对面向组件提供帮助. 面向组件编程更多的是考虑部署的概念, 如何从当前应用的类路径中动态找寻一个类,类路径下存在一个类的多个版本,应该如何确定加在的版本等等. 拥有组件框架对于应用程序开发者来说有极大的好处, 无论采用什么表现层框架,它都可以轻易的调用服务, 对它来说, 后台应用逻辑的更新, 持久层的更换也只是一个Jar包替换的问题. 如果表现层框架换了, 调用现有服务也是很容易的. Spring 基本上是采用XML来描述各个POJO 之间的依赖关系, 通过 Setter 或 Constructor 注入. 我觉得"在表现层依然采用 IOC 注入方式" 也是一个问题, 如果采用 Hivemind , 使用 Struts 做表现层,你可以通过 Service Locator 的方式获得服务, 而采用 Tapestry 你也可以采用 IOC 方式注入 Hivemind 服务. 这样对应用程序开发人员来说就非常灵活了. 所以我推荐大家采用 Hivemind 这种新型的 IOC 组件框架来开发 Java web 应用.

我发现大多数使用 Java 一两年的程序员都喜欢构造自己的"完美"组合开发框架, 我也是如此. 不过这方面做的最"过分"的当属 Matt Raible , 这位仁兄创建了 Appfuse 这个Starter 框架, 它结合了当今所有流行的框架加以组合来开发一个简单应用. 当初只是一个实验性的模型, 新的版本已经非常复杂了. Appfuse 也是采用三层组合,每层中都采用最流行的框架, 不过相对来说, 应用层和数据持久层采用的框架比较稳定,就是 Spring 和  Hibernate , 最近数据持久层又添加了 iBatis 和 JDO, 不过最丰富的当属表现层了, 当今主流的 Web 框架,他用了一遍, Webworks, spring mvc, struts, tapestry, jsf . 其实 Matt 现在做的有点过头了, 我承认我自己也是通过学习他的 Spring Live 一书来了解 Spring 开发的. 我对他那个相对简单的 equinox (Appfuse Light 版本)框架更感兴趣一点, 现在的 Appfuse 对我来说,已经膨胀到无法看下去的地步了.  其实他如此组合没有什么太大的意义,最终程序员都会有自己的"完美"方案的. 我的选择是 Spring MVC + Hivemind + Hibernate . 以前中间应用层我是采用 Spring , 因为 Spring 和 Hibernate 的结合比较完美, Template 的确简化了代码量,使用也很简单. 但是随着 Hibernate 3.x 的推出, 原有的 Checked Exception 都改为 Unchecked Exception 了, 使用 Spring 的优势就不那么明显了, 而组件框架部署的优势就成了首先考虑的问题. Spring MVC 是 Spring 框架中一个从头开发的子框架, 它的设计比较完善,功能很多: 多视图层输出, 向导页面等等, 如果你采用Flash 来制作 RIA 应用就可以采用 Velocity做视图层生成 XML 数据, 也可以利用 Spring 的 Webservice 集成功能. 这是我心目中一个完美的组合.

现在还有不少人正在继续构造自己“完美”的框架,其实这种追求完美的过程是永无止境的,正如那个广告中说的“没有最好,只有更好”。记得以前看《六人行》第一季中有这么一集,Chandler 公司的一个女同事说要给他介绍一个“完美”的男友,Chandler 说了这么一句话 “Ah, y'see, perfect might be a problem. Had you said 'co-dependent', or 'self-destructive'”,当时我并没有理解这句话的意思,现在觉得这似乎是描述这种追求完美框架行为最合适的一句话了。我想,可能有不少Java程序员跟我一样,打算寻找一个最完美的组合框架来做一个自己的应用,结果这个应用过了一两年都没完成。Java 程序员过于重视理论上完美的设计,而忽略实际的效果。看看如此众多的 Java 框架竟然构造不出一个好的论坛程序,所有出色的论坛都是出自 PHP 和 PERL,的确很 让 Java 程序员羞愧的。现在看来,真正的 Pragmatic Programmer( 实用主义程序员)都来自这些脚本语言的使用者,他们没那么多思想包袱,是真正笃信“实践出真知”的一群人, Java 社区应该多向他们学习。对于 Java 程序员来说,关键不在于你是否寻找到一个完美的框架组合,而是要做出一个好的应用。在论坛与人空谈,与人论战是没有任何意义的, David Heinemeier Hansson 建立 Rails 是在做项目的过程中产生的,他没有采用任何已有的框架,也没有去和别人辩论,而是靠实事说话把整个 Java 业界搅的天翻地覆。 我想这点值得广大 Java 程序员深思。

 
原创粉丝点击