2009年2月26日星期四

转账Transaction(交易/事务):业务层和持久层设计

在邱郁惠的《系统分析师UML实务手册中》曾读到,Coad的彩色UML模型在早期曾是以事务为中心的。在Coad后来的著作中,仍可以看到事务的烙印:aMomentInterval.commit()。

巧的是,在Prevayler的实现中,强调的也是事务:它要求开发者设计可以序列化的“事务对象”,利用Java的序列化机制实现持久,而所有对象的目前状态则保持在内存中。

三年以前我开始看Prevayler时,觉得它的这种“事务对象”不好理解,别扭,不是我喜欢的东西。在读了彩色UML建模之后,我理解了这种持久设计。下面以银行账户转账的例子来说明我的理解(注意,这个例子是虚构的,不存在于任何真实的系统中。如有雷同,纯属巧合):

业务模型

关于这个业务模型,有两点说明:
  1. 按照“5Ws and an H”的方式设计。MoneyTransferring本身记录了转账的金额和时间,说明了这是个什么事(What),何时发生(When)。MeansDesc是转账方式描述,可能是通过ATM机、柜台、电话银行等方式完成这次转账,说明了发生方式(How)。Terminal说明这次交易在哪里发生(Where)。Staff和Customer说明了参与这次转账的人(Who)。转账的理由(Why)则因为没有很合适的分类方法,可能作为MoneyTransferring的一个字符串属性。
  2. 所有的类,都可以作为分类统计的依据。例如,我们可以根据一个账户的MoneyTransferring事务和存入、取出事务,确定这个账户的活跃程度。或根据MeansDesc来确定用户对转账方式的偏好。“5Ws and an H”都可以在未来成为查询和统计的条件。
通过这个业务模型,我们记录了“流水账”,并为将来的数据仓库分析保留了足够的信息。某本书上曾说,数据库分为两种:操作型数据库和数据仓库型数据库。

持久设计
持久层设计为一个接口,其中的一个方法实现转账事务:
PersistentLayer.transferMoney(MoneyTransfering mt) ;

如果以关系数据库的方式来实现持久,这个事务将包含3条SQL语句:一条插入(INSERT)和两条更新(UPDATE)。更新必然是用原来的账户余额加上或减去转账的金额。数据库事务将保证这个操作的ACID。如果转出账户余额不足,且数据库设置了约束,余额字段不能为负,这个事务将失败。

但是,我们在开发过程中还可以临时使用内存的持久方式,实现一个“不持久的”持久层。这个持久层实现只是简单地将MoneyTransfering对象插入到一个全局的List中,并假定业务层已经将相应的两个账户做了改动。

这两种实现在对业务层的假定方面有一个细微的区别。第一种SQL实现只关注传入的MoneyTransfering对象拖着的两个Account对象的ID,而不关心它们的余额。第二种内存实现则假定业务层已经处理好了它们的余额。如果我们只用内存实现来做一些不严肃的测试,这样也未偿不可,但严格来说,我们对持久层接口的契约理解上,出现了偏差。

所以,正确的做法是,业务层不负责改动账户余额,全部交由持久层实现来完成。这样内存持久实现将做3件事,一次插入和两次更新。这下和数据库持久的语义一致了。

之所以费这么大劲讨论内存持久对PersistentLayer接口语义的支持,是因为有Prevayler的存在。它把“内存+对象序列化”做为一种正式的持久方式,用于生产系统中。如果我们写一个PersistentLayerPrevaylerImpl,那么这个实现对这个事务的处理是:将MoneyTransfering对象插入全局的List,更新两个账户余额,将MoneyTransfering对象通过序列化机制持久到硬盘。内存里保存的是最新的数据快照,持久存储上存放了所有数据变动的历史。这和HSQL是一样的道理。

您可能听说过DB4O,或者听说过BerkeleyDB,还可能听说过Tokyo Cabinet。在云计算时代,我们有很多的持久机制可以选择。为什么不把业务代码设计得灵活一些,可以适应不同的持久机制呢?多维护一个PersistentLayer接口的开销并不算大。

小结
  • 我们用“5Ws and an H”的方式对业务事件建模。
  • “5Ws and an H”成为业务数据查询和统计的条件。
  • 通过PersistentLayer接口来统一持久层语义。
  • 根据不同目的和环境,采用不同的持久层实现。
  • 云计算时代的持久不同于C/S时代的持久。

2009年2月17日星期二

Peter Coad的7项修炼

(本文已在《程序员》杂志2009年第2期发表)

Peter Coad是我喜欢的一位面向对象专家和软件创业者。他在上世纪九十年代与人合著了6本关于面向对象软件的分析、设计和编程的书籍,以《面向对象分析》一书中和Yourdon共创的Coad/Yourdon方法而成名。1999年,他创建了TogetherSoft公司。2003年,TogetherSoft卖给了Borland公司,他成为了Borland公司的副总裁。后来,他离开了Borland,兴趣转向了软件之外,教人读希腊文版的圣经。

他最值得一提的技术贡献,就是提出了“彩色
UML”建模方法,这也是他最后一本书《彩色UML建模》的主题。在这种建模方法中,他使用了4种颜色,代表4种不同设计意图的类,创造性地回答了面向对象分析和设计的一个根本问题:“怎样识别类,并确定方法和属性?”这本书已由机械工业出版社引进版权,出了中文版。

我对
Coad的关注始于十多年前,那时朋友向我推荐了北大出版社引进的《面向对象分析》一书。后来因为学习JavaUML建模、关注Borland公司的产品,又看到了TogetherSoftCoad的名字。再后来,敏捷方法学兴起,我又看到了Coad,他是特征驱动开发(FDD)的主要支持者。向师傅学习,要学师傅练功的方法,而不只是师傅的招式。我想知道的是:为什么Peter Coad会发明彩色UML的方法?或者说,Peter Coad是怎样炼成的?

在他
1994年左右的一篇文章中,对这个问题给出了一些提示。这篇文章标题是“Amplified Learning”,介绍了“7种智能”的理论和应用。

语言是第一种智能。
著名的通天塔的故事体现了自然语言表达的重要性:失去了语言沟通能力,团队协作就变成了不可能的任务。语言能力的习得,有一句话可以概括:“听说领先,读写跟上”。学习本国语言和外语都是如此。语言的重要性,实际上体现的是沟通的重要性。据说,当年
TogetherSoft公司在面试时,会先让被面试的人选一个与软件开发无关的话题讲15分钟。通过这样的面试环节,可以了解面试者对生活的一些理解和表达沟通的能力。这里我还想强调一下中国程序员的英语能力。由于软件方面的最好的信息几乎都是英语的,所以良好的英语能力对程序员有很大的帮助。一般来说,在各行各业,最成功的人都是拥有最好信息的人。

代码的可读性也是语言能力的体现。Knuth1984年提出的Literate Programming,把编程比喻为写文学作品。既然是文学作品,就会有不同的风格。有晦涩难懂,也有深入浅出、生动有趣。所以作为一名好的程序员,需要对自然语言和编程语言都有深刻的理解,要懂得欣赏和创造文章之美,代码之美。

那么什么样的文章是好文章?一个留美回来的英语作文教师曾对我说,好文章首先要有“
something new(新东西)”。新的观点、新的解释、新的视角、新的实践、新的证据......都可以。这是西方鼓励创新的思路,所以我们会看到软件业的创新层出不穷。中国人对好文章的评判标准更注重传统。《文心雕龙》里提出的标准是“本乎道,师乎圣,体乎经,酌乎纬,变乎骚”。把软件提升到道的高度,那就是C++创始人Bjarne Stroustrup所说的,“人类文明运行于软件之上”。

数理逻辑是第二种智能。我们需要对数字敏感,需要具有归纳和演绎的能力。这样的能力虽然也包含在语言学习之中,但更多地包含在数学、物理等理科课程中。在西方,学理科的学生几乎都读过
Pólya的《怎样解题》一书。数学是思维的体操,它让我们的思维更灵活、更缜密。懂得欣赏数学之美的人,会看到简洁的公式和逻辑的力量。许多大学的计算机系都是从数学系分出来的,一些老师以前是学数学或物理的。史书记载:“尧造围棋,教子丹朱”。学围棋也是对逻辑思维很好的锻炼。和数学一样,从围棋中我们也可以看到,从一些简单的规则出发,可以怎样建造一个瑰丽的宫殿群。另外,在软件复杂度测量、软件工程管理方面,也需要许多量化的测量指标。

以上两种智能可能是学校教育中教授得最多的内容,体现在语、数、外三门主课上。能够熟练灵活地运用语言进行沟通,并能够运用逻辑推理来找出问题和解决问题,我们就具备了一些基本的能力。但除此之外,还有一些扩展性的智能,对于我们软件开发者也很重要。

空间色彩是第三种智能。
九十年代是
GUI的十年,在这十年里,软件的美由内而外,扩展到了视觉领域。叔本华说,“美是最高级的善,创造美是最高级的乐趣”。但是,我们不时会看到一些殊无美感的设计,那真是叫“乏善可陈”。Apple公司的操作系统中,利用色彩来表示窗口的控制按钮,给人以艺术的享受。Peter Coad的彩色UML建模方法利用4种色彩来表示4种不同的架构型,通过色彩强化了信息沟通的效果。我还记得当初在Borland公司的集成环境中第一次看到彩色语法制导时,带来的那种震撼。作为程序员,我们经常需要阅读图表、画设计草图、运用图表或幻灯片来传达信息,空间色彩能力也是相当重要的。

“流光容易把人抛。红了樱桃,绿了芭蕉。”“知否?知否?应是绿肥红瘦。”中国人讲究“诗中有画,画中有诗”。软件是散文,是诗,其中也蕴含着色彩和空间之美。

音乐是第四种智能。科学家有音乐特长的例子比比皆是,并且他们都认为,音乐对他们的专业是有启发的。
Spring框架的作者之一Rod Johnson拥有音乐博士学位。旋律和节奏是音乐训练的两个主要方面。很多歌曲要记住歌词不容易,但旋律却让人记忆深刻。我喜欢在工作环境中使用背景音乐,这样能舒缓压力,激发创造力。音乐反映了我们对世界的一种认知模式,反映了道。《庄子·养生主》载,“庖丁为文惠君解牛,手之所触,肩之所倚,足之所履,膝之所踦,砉然响然,奏刀騞然,莫不中音,合于桑林之舞,乃中经首之会。”庖丁是个解牛的专家,是个哲学家,也是个音乐家。

运动锻炼是第五种智能。许多职业棋手都通过运动锻炼,保持有活力的工作和思考,使自己处于最佳状态。极限编程(
XP)在一开始提出“每周工作四十小时”作为一种最佳实践,但是如果你的身体有问题,就会连四十小时都做不到。后来XP将这一实践改成了“保持有活力的工作”,我想大概是因为有些身体好的人,每周工作六十小时也一样很有效率吧。要养成锻炼的习惯,争取做到“每天锻炼一小时,健康工作五十年,幸福生活一辈子”。

以上三种智能,体现在音、体、美这三门副课上。
将这三种智能提升到一定水平,就可以称得上全面发展了。

自知是第六种智能。曾子曰:吾日三省吾身。苏格拉底说:“认识你自己”。蘧伯玉年五十而知四十九年非。正确地认识和评价自己,不断地反省自己,这是进步的基础。《从优秀到卓越》一书的作者
Collins说,你应该问自己一个问题:在哪个领域你可以做到世界前三?人贵有自知之明。

知人是第七种智能。
越来越多的工作,正由团队来承担。有一本书名叫“
Teamwork Is an Individual Skill”,书名就很有启发:团队合作是一项个人技能。要成为团队的一员,你需要读懂他人,并具备一定的社交技能。然后,通过实现团队的目标来实现自我。“己欲立而立人,己欲达而达人。”如果你是团队的领导,那么知人善任就是一项重要的能力。刘邦正因为用对了张良、韩信、萧何而得到了天下。

寻找怎样的人作为合作的伙伴或朋友?益者三友:友直、友谅、友多闻。怎样看人才不会看走眼?子曰:视其所以,观其所由,察其所安;人焉廋哉。人焉廋哉。

以人为镜,可以明得失。见贤思齐焉,见不贤者自省也。所以,知人也可以促进自知。

知人者智,自知者明。以上两种智能,就属于情商的范畴了。(透露一个秘密:一下记住
7件事是有难度的,所以我把它分成3个部分——主课、副课、情商。你能记住吗?)

每个人在这
7种智能上的发展都不尽相同。可能因为各自的成长经历和条件,我们在某些方面的智能没有得到很好的发展。随着年龄的增长和自省智能的提升,你可能会认识到人生就是练功、升级。如果你现在已经走上了一条主动的持续改进之路,那么你打算如何在这些方面提高自己呢?

2009年2月10日星期二

‘5Ws and an H’流水账和组件

在前面的贴子“彩色UML建模、SOA和《目标》”中,提到了‘5Ws and an H’流水账,这里进一步阐述一下。

流水账
  • When:这个业务事件何时发生?
  • Who:谁参与这个业务事件?
  • What:这是个什么事?涉及什么物品?
  • Where:这个业务事件发生在哪里?
  • Why:为什么会发生这个业务事件?
  • How:这个业务事件以何种方式发生?有无细节?

让我们来看一个例子:
特征:为客户打印发票(收据)
  • When:记录下“打印发票(收据)”这个业务事件发生的时间
  • Who:参与这个业务事件的人有两个:客户和业务员
  • What:这个业务事件是“打印发票(收据)”,涉及到一张纸质发票(收据),上面有序列号
  • Where:这个业务事件发生在公司旗下众多连锁店的一个里
  • Why:因为客户付了一笔款,客户付款是前驱业务事件
  • How:用户付款时享受了折扣优惠,需要在发票(收据)上注明

当我们把一个业务事件的这些方面都记录下来时,信息就完备了。值得一提的是,在具体业务事件中,上面的每个因素都可以有多个具体的值。例如,在持续集成中,因为前面有3次提交,然后在5分钟内没有新的提交,这触发了一次持续集成。那么,这次持续集成事件的前驱业务事件就有3个。

组件
这个业务事件构成了一个组件,它对使用的上下文做了一些假设。如果这些假设没有变化,这个组件就不需要改变,可以复用。

也可以把这个组件看成一个独立的数据源,它提供对这个数据源的数据分析统计功能。例如:
  • 这个月哪个业务员开出的发票最多?
  • 从上次领发票本到现在,某个连锁店开出了多少发票(是否要提醒领新的发票本)?
如果从流水账和总分类账的观点来看,可以认为“5W and an H”类似于“科目”,它们对流水账进行了分类。

组件粒度
组件粒度也就是业务事件的粒度。“进行一次销售”这个业务事件可能由“下订单”、“付款”、“打印发票”这3个小粒度的业务事件组成。 于是,我们可以在“进行一次销售”这个业务事件的“How”部分,标明它由3个明细业务事件构成。如果我们还允许一周内无条件退货,那么还可以包含“退货”的业务事件。

小粒度的组件组合起来,就构成了大粒度的组件,系统以这种方式体现伸缩性。这样做的好处在于:
  • 简单一致地伸缩,设计小系统和设计大系统的原则一样
  • 在不同的抽象层面上看系统,如操作层面和管理层面

总账会计师和CIO
总账会计师要能够从企业的总账中分析出企业的盈利能力和风险。CIO要能够从业务事件流水账中按“科目”进行整理,通过分析找到实现企业目标的风险和机会。

2009年2月8日星期日

RESTful的停车场图和开发Dashboard

“REST的原则包括:
  • 以资源为中心的方式。
  • 所有相关的资源都可以通过统一资源标识符(URI)来寻址。
  • 统一通过HTTP来访问:GET、POST、PUT和DELETE。
  • 内容类型协商允许从同一个URI取得不同表示形式。
  • REST风格的服务很容易通过运行在web浏览器、其他客户端或服务器上的代码来访问,这在Ajax的背景下很流行。
  • 完全利用WWW的缓存机制
  • 为同一资源的多种表示层提供服务。
简而言之,这些网络架构原则允许多个网站对用户的无缝展现。由于有了REST,因特网变成了一个动态的网络,而不只是点到点的连接,不只是基于远程方法调用(RPC)技术。”

以上内容引自正在译的新书“Executing SOA”,这启发我用REST的观点来看问题。REST不是新观点了,我现在才有点理解,Shame on me!我的博客中“最近的访客”就是一个极好的REST例子。

如果用REST的观点来看,项目的进度是一项资源。特征驱动开发(FDD)的停车场图就可以作为一个很好的例子。
  • GET:取得停车场图(图或数据)
  • POST/PUT:创建或更新这个停车场图所代表的资源
  • DELETE:删除这个停车场图所代表的资源

然后,我们就可以把这个停车场图无缝集成到别的网站中。集成到哪里呢?项目开发Dashboard!类似像Sonar或maven生成的项目网站这样的。

更进一步,如果所有的资源(版本控制、持续集成......)都放在云端,然后用RESTful的方式发布出来,我们就可以随便用一个内容管理系统(CMS),定制自己的项目Dashboard,最简单的可能就是使用一个wiki。

云时代真的来了。很想写一个停车场图的RESTful Web Service。


2009年2月7日星期六

测试计划

关于制定test plan,我理解的是测试工作的进度安排。

最根本的一条原则:测试工作和开发工作结合得越紧密,效果就越好。

理想中的测试工作安排是这样的:
  • 在项目早期,测试团队的骨干参与需求工作,确保需求的可测试性,同时也帮助消除了需求的二义性。
  • 在概要设计/架构设计阶段,测试团队同步进行测试概要设计,确定测试的基础架构和基础设施。
  • 在详细设计/迭代开发阶段,测试团队同步进行测试用例设计和开发,确保开发团队通过持续集成交付的每个feature都在最快的时间内得到测试。容量测试、压力测试等也要在条件一成熟,就马上进行。

随着产品代码的不断增加,自动化测试的代码也不断增加。这些测试代码在整个软件生命周期中都有用。

测试成本低的的设计是好设计,测试成本高的设计是不好的设计。降低测试成本是降低软件总体拥有成本的根本途径。

人总是会犯错误。尽早发现,尽早更正,确保决不再重犯同样的错误,是我们的追求。

参考书籍:《快速测试》、《持续集成》、《Java测试新技术:TestNG和高级概念

2009年1月26日星期一

彩色UML建模、SOA和《目标》

彩色UML的主要思想是组件化。粉红色的MI(时刻时段)是组件的核心,同时粉红色的MI也代表着事务,这些事务串在一起,组成了流程。

每一个粉红色的MI及其相关的黄色角色、绿色参与方-地点-物品和蓝色描述构成了业务流程中的一个步骤。根据彩色建模的伸缩性设计,这个步骤的粒度可大可小。可以是大粒度的“完成一次销售”,也可以是小粒度的“开发票”或“收款”。

SOA的思想是要把事务组件web service化,再用BPEL来组织流程,以这种方式来组织业务逻辑。
另外,在数据逻辑或信息逻辑的方面,SOA会把一些数据分析和挖掘的方法也设计成可以复用的web service。

彩色UML方法里面的一些思想,比如“评估销售的及时性”,就带有很大的数据分析的味道。

换句话说,我们先对业务过程记上流水账,然后再对流水账进行分析。我们记录下When/Who/What/Where/Why and How(‘5Ws and an H’ ),然后就可以进行各种分析了。你可以在数据库中只记录下最小粒度的业务事件,然后以虚拟数据源或信息服务的方式,提供大粒度的业务事件的相关信息。

为了让流程能够灵活变化,我们可以考虑消除步骤之间的耦合,即在红色的MI之间不建立直接关联,而是利用它们共同的关注的内容,如“订单”,来组织流程。如果我们要“评估销售的及时性”,只要得到“订单”下单的时间和付款的时间之差。设想有一个信息服务,作为一个虚拟数据源,提供一段时间内所有订单的完成时间和各步骤完成的时间。

Goldratt的《目标》是所有管理者都应该读的一本书。其中提到了一个重要观点:所有在原材料方面的投资,如果还没有变成有效产出(产品卖出去并收回货款),都应该看成是负债。这种负债乘以天数,是一个重要的测量指标。我们应该努力减小它。根据这一观点,制造业的企业提出了许多解决方案,如按订单生产、精益制造等等。而制造业的人们学到的东西,看来IT业都要重新学习。

回到我们的主题,这和彩色UML或SOA有什么关系?

关系就在于:我们的IT系统,要能够帮助企业的决策者找到系统瓶颈。看过《目标》的人应该记得,那里面有个神奇的CIO,每次都能告诉厂长系统的新瓶颈出现在哪里。然后大家不断优化瓶颈,使系统的产能和效率不断提高。

如果您就是那位CIO,请你告诉我,我们的订单执行过程中,哪(几)个环节耗时最多?




2009年1月15日星期四

设计决定、反悔、霰弹式修改和架构污染

(老文章,曾在《程序员》杂志发表。用了vim得到彩色的代码,但可惜在《程序员》上没有印出彩色。)


设计决定

“好吧,在这一个项目中我们就使用Google Guice!”我们坚毅、果敢、英明神武的首席架构师宣布了这个决定。

为什么不呢?依赖注入是这么成功的一个概念,我们使用Spring作为依赖注入框架已经好多年了。而Google Guice又给我们带来了许多的改进。

首先,Guice抛弃了XML配置文件,这使它具备了“重构友好”的特征。在Guice中,我们通过Java代码来说明接口(服务)和实现类的绑定,例如下面这一段代码:

public class MyModule extends AbstractModule {
protected void configure() {
bind(Service.
class)
.to(ServiceImpl.
class)
.in(Scopes.SINGLETON);
}
}

这样,当我们需要重构(修改)接口或实现类的名称时,就可以利用集成开发环境提供的重构功能来完成,不需要再手工修改XML中的类名。在重构深入人心的今天,我们不希望改一个类名还需要额外的工作。

第二,在抛弃了XML配置文件之后,应用程序就不再需要一个XML解析器了。这样,Guice就比Spring“瘦身”了不少。高手们总是对减少资源的占用怀有特殊的爱好,不是吗?Guice 1.0Jar包只有544K(高手可能会认为还是大了一点),不依赖于其他的Jar包。另外,由于不需要读取并解析XML配置文件,执行的速度也提高了不少。

第三,Guice使用了JDK 5的新功能,其中一项就是泛型。我们不再像以前使用Spring那样进行强制类型转换了,我们有了编译时刻的类型安全检查,而且代码也简化了。我们像下面这段代码那样取得组件实例:

public class MyApplication {
public static void main(String[] args) {
Injector injector = Guice.createInjector(
new MyModule());
Client client = injector.getInstance(Client.
class);
client.go();
}
}

第四,Guice使用了JDK 5的另一项新功能,annotation!这个词太可爱了,听起来和innovation有点相似,以至于我们一时间不知道如何将它翻译成中文。Guice提供了近乎神奇的@Inject annotation,满足您各式各样的注入需要!(我相信这些注入方式都是在Martin Fowler的那篇著名文章中提到的。)

我们可以实现构造方法中的注入,像下面这样:

public class Client {
private final Service service;

@Inject
public Client(Service service) {
this.service = service;
}

public void go() {
service.go();
}
}

也可以对属性直接注入,甚至不需要设值方法,像下面这样:

public class ServiceImpl implements Service {
@Inject
Emailer emailer;

public void go() {
// Some expensive stuff.
...
// Send confirmation.
emailer.send(
...);
}
}

第五,Guice有在真实应用中成功使用的历史。据说,它曾在Google的广告应用程序中使用,而Google的广告应用程序可能是目前世界上规模最大的应用程序!

最后,更重要的是,品牌就是品质。Google是如此受人尊敬的公司,吸引了许多重量级的人物加盟,已经成为许多IT人士向往的地方。使用Google的荣誉产品,就像使用JDK自带的包那样可靠,我们还有什么可担心的?据说Spring的新版本也向Guice学习,使用了annotation。模仿就是一种肯定,不是吗?


反悔

“新郎,你愿意娶新娘为妻吗?”

“是的,我愿意。”

“无论她将来是富有还是贫穷、或无论她将来身体健康或不适,你都愿意和她永远在一起吗?”

“是的,我愿意。”

“将来看到更漂亮、更好、更合适的,你也不会改变今天这个决定吗?”

如果理性地回答,答案就太不浪漫了。因为未来不可预见的事情实在太多。好在热恋中的人们倾向于不那么理性,所以答案是“永远。如果非要加上一个期限,我希望是一万年”。

我们的架构师正在与Guice开始一段新的感情。这种感觉太好了,上次有这种感觉好像还是在刚使用Spring的时候,那已经是几年前的事情了。现在他压根没想到以后可能会像抛弃Spring一样抛弃Guice

直到有一天,一个刚刚参加工作的小兄弟问了他一个很傻的编译错误。他告诉这个小兄弟,要使用@Inject,必须这样:

import com.google.inject.Inject;

小兄弟满意地继续他的开发,但我们的架构师却觉得有一丝不祥的感觉掠过,空气中似乎飘荡着一种坏味道。

他检查了一下项目源代码。在第一次迭代中,我们写了50多个类,其中大约有一半都包含了@Inject!这种情况不妙,简直太不妙了。这违反了他多年的经验所建立起来的原则信仰。

这违反了Larry Constantine的“高内聚、低耦合”的原则。这条伟大的原则如此简单而深奥,以至于不断受到人们的践踏。

虽然“疯狂的Bob Lee”为Guice设计出了极小的API接口,但在使用@Inject这一点上,却让人不太赞同。他似乎鼓励我们在自己代码的各处插入@Inject

当我们把一个设计决定分散到代码的不同地方,它就会成为难以改变的既成事实。即使是对于JDK自带的类,我们也不能这么做。我们不能在所有的业务类中都引用JDBC的包,使用ConnectionStatementRowSet。相反,我们应该把它们放在持久层中。也许,我们应该使用Hibernate这样的框架,将它们封装起来。也许我们还需要再封装一层,设计一个PersistentLayer接口和一个PersistentLayerHibernateImpl实现。这样,如果我们以后有新想法,只要再写个PersistentLayerJdbcImplPersistentLayerDb4oImplPersistentLayerIbatisImplPersistentLayerHibernateAndIbatisMixImpl

如果我们把使用Guice这个设计决定作为一种默认的假定,让它散布在代码的各个角落,那么将来我们要反悔的代价就非常大。实际上,我们就不能反悔了。

我们会反悔吗?我们需要反悔吗?不能反悔的决定是我们可以承受的吗?

我们的架构师做出了一个痛苦的决定:我们需要能反悔。毕竟,作为一名架构师,他的全部声誉就在于设计出有弹性的架构,可以容纳将来可能的变化。


霰弹式修改

我们的架构师是经验丰富的。办法总比问题多,不是吗?关键是要发现问题。根据“所有的问题都可以通过添加一个间接的中间层来解决”这一原理,他设计了一个Factory,这是一个全局的Singleton

public class Factory {

private static Factory factory;

synchronized public static Factory getInstance() {
if (factory == null) {
factory =
new Factory();
}
return factory;
}

private Injector injector;

private Factory() {
injector = Guice.createInjector(
new MyModule());
}

public T getInstance(Class type) {
return injector.getInstance(type);
}
}

然后,在每个用到服务依赖注入的类中,在构造方法里加上赋值语句:

public class Client {
private Service service;

public Client() {
this.service = Factory.getInstance().getInstance(Service.class);
}

public void go() {
service.go();
}
}

注意,我们没有为service属性加上final关键词,这样您仍可以有setService()方法,可以利用Mock对象对Client类进行单元测试。

剩下的工作就是从项目已有的代码中去掉所有的@Inject。这是一种“霰弹式修改”,不幸中的万幸,我们只要修改20多个类,而不是200多个、2000多个。

由于有自动化的测试类作为保障,这种改动很快就完成了。如果以后我们想换一个依赖注入框架,就不需要“霰弹式修改”了。我们甚至可以很容易改回用Spring,当然,目前还没有这个必要。

再见了,@Inject


架构污染

不久后的一天,我们的架构师在浏览代码时,惊奇地又看到了@Inject

原因很快查到了,一个刚刚结束了婚假回到开发团队的开发人员,因为错过了关于这次修改的讨论,重新在代码中引入了@Inject

我们的架构师设计出的精美架构受到了污染。这种事情以前也发生过,似乎总是有各种各样的原因,导致架构在实现者手里变形。我们的架构设计师能设计故事的开始,却猜不中故事的结局。这种情况是他所痛恨的。

“我们需要有一种手段,能对架构的实现进行监控。”架构师在开发例会上说,“这不是不相信我们的程序员,人都可能犯错的。”

“信任,但要核查。”里根在签署核裁军条约时曾这么说。

一个对敏捷软件开发颇有研究的高级程序员说,“我最近看了一本书,书名是《持续集成》,书中提到了一个工具,好像叫JDepend,可以显示项目中的包依赖关系。正适合解决这个问题。”

后来的故事简单了,通过重构,我们将Factory放在了单独的一个包里,只有这个包才对Guice产生依赖。我们的架构师经常地查看JDepend的输出,看着他设计架构一天天地实现。


尾声

我们的架构师盯着他的二十二寸宽屏液晶显示器,回味着简洁优美的架构所带来的快感。一个念头在他脑海中闪现:“如果Guice不提供@Inject相关的功能,那Jar包就会更小了。”

Guice是开放源代码的,如果有时间,我们的架构师将替他的“新娘”瘦身,去掉@Inject相关的所有类,让这种架构污染永远也不可能再发生。但是现在,还有更多的项目工作等着他。也许等他忙完这段时间,会再来折腾一下这个问题。

生命在于折腾,不是吗?