You are on page 1of 40




在大型遗留系统基础上运作重构项目

印第安人的灵魂——敏捷回顾
异地分布式敏捷软件开发

McDonald & Scrum

1
. .
目 录
目录


捷 05 印第安人的灵魂——敏捷回顾

之 印第安人在赶了 3 天路后,会停下来小
憩一天,因为他要等着自己的灵魂跟上来。敏捷开发在

道 经历了一次迭代或者冲刺(Sprint)后,也需要休整,
以等待团队的灵魂跟上来,这一过程被称之为“敏捷回
顾(Agile Retrospectives)”。
08 解开最后期限的镣铐

14 在大型遗留系统基础上运作重构项目
本文以 ThoughtWorks 中国公司与客户
合作的咨询项目为背景,为读者介绍如何在一个大型遗
留系统的基础上组织和运作重构项目,从而切实有效地
改善系统质量。
23 单元测试实践小结
30 异地分布式敏捷软件开发
异 地 分 布 式 软 件 开 发 ( Distributed
Software Development)是指由多个位于不同地理位置
的团队进行同一个软件项目的开发过程。

34 McDonald & Scrum


Scrum 是一种敏捷方法,强调快速反应,
讲求人的配合等等。而其团队组织方式是多功能型,由
具有各种才能的人组成足以达成既定任务的团队。

36 欲善敏捷开发 先利敏捷工具
敏捷开发的潮流并不是由敏捷工具来
推动的。但近年来,为了更好地支持敏捷开发,敏捷工
具也有了很大的发展。

40 敏捷软件开发:原则、模式与实践

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

2
3

关于捷道·敏捷堂

捷道·敏捷堂

捷道之意,即为“敏捷之道”也。自然,终南捷径,亦为敏捷方
法所期许,若能创造软件项目开发之“捷道”,不亦快哉!

Don 在西班牙语里是“先生”、“导师”之义。杨绛先生在《Don
Quixote》中,将 Don 翻译为“堂”,于是,吉坷德先生就变
成了堂吉珂德。堂的中文意思是“大厅”,我们常常说登堂入室,也就意味着你入门了,已经进入
了大厅。故而,Agile Don 之为敏捷堂,实意味隽永也。

“敏捷方法”本为舶来品,追求的是灵活、小巧、敏捷地应对软件开发过程中的变化,而不像某些
重量级开发方式那般笨拙不堪,流于形式,而忽略了软件开发的变化万端。敏捷重思想、重精神、
重原则、重实践,而轻形式、轻过程、轻方法、轻管理,讲究的是敏捷为本,交流至上,持续改进,
因地制宜。若体会了敏捷思想,只要遵循敏捷的基本原则,各种方法皆可敏捷。若未曾领会敏捷的
真谛,那么即使应用了敏捷方法,也不过是“空有其形,大失其意”,终究是“画虎不成反类犬”!

因此,敏捷最重实践。莫要天真地以为掌握了一种敏捷方法,就可“放之四海而皆准”。敏捷方法,
本身也是需要持续改进的。

捷道·敏捷堂期望汇聚中国活跃在软件开发领域的敏捷专家与敏捷爱好者,济济一堂,共同探讨,
共同展望敏捷未来。如此,余愿足矣!

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷堂作者

敏捷堂作者

熊节是 ThoughtWorks 公司的 Checklists》和《Grails 入门指南》的翻译。


咨询师,曾参与《重构:改善既 个人也是敏捷的爱好者,致力于将敏捷推向实
有代码的设计(中文版) 《J2EE
》、 践。
核心模式(原书第 2 版)》、
《Contributing to Eclipse 中
文版》等图书的翻译。目前正在从事 Ruby on
Rails 的项目,并致力于敏捷方法与思想的推广。 李默,ThoughtWorks 公司咨
询师、业务分析师,敏捷过程教
练 , BJUG(www.bjug.org) 创
张逸具有多年的软件开发与设 始人之一。网名为“冰云”。目前主要专注于软
计经验,他是两届微软最有价值 件需求管理与市场营销的协作、产品交互设计、
专家(MVP),著作/译作包括 组织过程改进等方面的内容。
《软件设计精要与模式》、
《WCF 服务编程》。目前,他
主要从事 SOA 企业信息解决方案的设计与研
究,以及敏捷方法的推广与实践。张逸是捷道·敏
徐毅目前在一家大型欧美电信
捷堂的创建人。
企业工作,具有丰富的软件开

林仪明,网名“Anders 小明” 发、软件测试及测试自动化经

(yimlin)。有数年企业应用软 验,同时,具有丰富的 Scrum

件的设计与开发经验,面向对象 开发模式经验,目前担任公司的

和面向方面技术的爱好者。参与 Scrum Master。专注于如何使 Scrum 顺利地

过国内开源项目 SpringSide 的 如何创建一个 Scrum 的生态系统,


融入企业中,

设计开发,参与了 InfoQ 的迷你书《Scrum 以及敏捷开发中的测试。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

4
5

印第安人的灵魂——敏捷回顾

印第安人的灵魂——敏捷回顾
张逸

印第安人在赶了 3 天路后,会停下来小憩一天,因为他
要等着自己的灵魂跟上来。敏捷开发在经历了一次迭代
或者冲刺(Sprint)后,也需要休整,以等待团队的灵
魂 跟 上 来 , 这 一 过 程 被 称 之 为 “ 敏 捷 回 顾 ( Agile
Retrospectives)”。敏捷回顾与项目总结会议不同,
它并非项目结束之后的盖棺论定,而是在项目过程中,
通过回顾会议及时总结上一次迭代中的得与失,以期达
到改进项目开发、团队合作等敏捷活动的目的。

如果将项目开发比作是一次征途,那么在项目中期的短期休整是很有必要的。然而这种休整并非是
将团队成员集体拉出去腐败一次,或者到 K 厅去鬼哭狼嚎一番,以泄心中的郁闷,如此种种只能说
是身体心灵的休息与放松。就像是运动员在比赛期间,队医的按摩、擦汗的毛巾、解渴的饮料。这
些重要吗?当然重要,放松疲惫的身体与心灵,方能更好地走向更远的目标。但更重要的是灵魂的
“反刍”,就像教练员针对运动员在上一局比赛的盘点与指导,指出选手以及对手的优与劣,从而
制定出后面比赛的对策,方能把握取胜之钥。

敏捷回顾不是一场没有主题的讨论会,大家坐下来,七嘴八舌漫无目的的一阵“乱弹”,这样的形
式对于项目进展没有任何帮助。Scrum 对于回顾有一个主要指导原则,这也是敏捷回顾的“最高指
导原则”:

无论我们发现了什么,考虑到当时的已知情况、个人的技术水平和能力、可用的资源,以及手
上的状况,我们理解并坚信:每个人对自己的工作都已全力以赴。

——敏捷回顾之“最高指导原则”

听起来,有些像一团和气的“和稀泥”做法,这样的原则会否让回顾会议的参与者一个个都变成好
好先生呢?难道我们一定要善意地评价团队中的害群之马,对他们的过错视而不见,使其“逍遥法
外”,并天真地以为我们的好心能够感化他们?难道我们要在项目开发中建立一个乌托邦式的大同
世界,同薪同酬,为了团队利益而抹煞团队成员之间的个体差异。

坦白说,我反对在进行团队成员评价时,采用这么一条“最高指导原则”,但这样的看法已经偏离
了攻击的靶子了。需要知道,“最高指导原则”是应用在敏捷回顾中,而敏捷回顾的最终目的是学
习,而不是绩效评审活动[1]。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷思考

如果敏捷回顾没有确定这条“最高指导原则”,用来倡议团队成员信任自己的伙伴,就会让回顾会
议成为互相攻讦、互相推诿的批斗大会,忘记了我们召开回顾会议的初衷。“最高指导原则”就是
为回顾会议竖立一个标杆,那就是在项目开发中没有破坏者,没有替罪羊,没有关键人物,只有整
个团队的利益。虽然某个人或许在上一次迭代中出现了错误,但我们会善意地相信此人之所以犯下
错误,并非有意为之,或者消极怠工,而是囿于当时之识见、经验、技能。我们的回顾会议必须指
明这些错误,并试图总结出最佳实践以避免在下一次迭代中犯下同样的错误,而“最高指导原则”
则能够消除因为错误的指出而给成员带来的负疚感,消除同事之间可能因此出现的隔阂与误解。换
句话说,回顾会议提出的所有批评都应该是“对事不对人”。

我曾经在一个项目的回顾会议上,听到了测试经理对于某开发小组产品质量的评价,认为该小组开
发的模块出现了太多的 bug。经过回顾会议的认真分析,最后得出的结论是该小组 Leader 迫于进
度压力,因而盲目地追求开发进度,却忽略了保障代码质量的单元测试覆盖率。于是,我们仔细讨
论了下一次 Sprint 的 Sprint Backlog,根据团队成员的能力进行了合理的分配。果然,在下一次
Sprint 中,该小组开发的模块出现的 bug 率降低了差不多 50%,即使 bug 总量并不大,这样的比
例仍然是非常可观的。而且,由于项目组成员都明白回顾会议的原则,因此并没有产生团队成员之
间的不快,开发人员和测试人员仍然能够合作得非常愉快。

在《Scrum Checklists》中,指明了 Scrum 回顾会议的议程:


1、在白板上写上主要指导原则;
2、在白板上画上一个至少三页纸连在一起长的时间轴;
3、在白板上写上“我们的成功经验是什么”;
4、在白板上写上“有什么能够改进”;
5、在白板上写上“谁负责”,然后分成两个区域——“团队”和“公司”。

从以上的步骤可以看出,敏捷回顾的主要工作就是明确目标、持续改进、处理问题。敏捷开发之所
以采用迭代的方式,实际上是利用蚕食方式逐步完成开发任务。将一个宏伟的目标切割为一个个小
目标,会给与团队成员更大的信心,并能够更加清晰地明确目标。而每次迭代后的回顾,则使得团
队成员可以更加清晰地明确我们在这个征途中,已经走到了哪里,未来还有多远的路程,就像印第
安人那样,等待自己的灵魂,否则就会不知身在何处了。

在项目中持续改进至关重要。所谓“取其精华,去其糟粕”,唯有如此方能够去芜存菁,提高敏捷
团队的战斗力。每一个敏捷团队都不可能是十全十美的,要么是在技术上存在个体差异,要么就是
缺乏足够的领域知识,或者,还未曾找到符合团队现状的开发方法(即使采用敏捷方法,也需要因
地制宜,切忌生搬硬套。即使现在符合,也不等于永远符合,即使符合这个项目,却未必符合下一
个项目。敏捷方法不可能放之四海而皆准)。即使这些均已具备,那么团队成员之间的磨合也并非
一朝一夕之功。若没有持续改进,团队就会像生锈的刀刃,不仅会褪去摄人的光芒,还会逐渐钝化
腐朽。

在项目过程中,有一个原则是处理问题越早,那么付出的代价与成本就越小。问题是,当我们在紧
张的开发任务中,有时候很难发现这些错误,更加意识不到这些错误会带来严重的影响。通过回顾

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

6
7

印第安人的灵魂——敏捷回顾

会议,利用团队成员互相善意地“敲击”对方,或者反复“锻炼”开发过程与方法,就能够让每一
位成员都炼就“火眼金睛”。在会议中,我们经常会发现,一旦某个成员发现了一个问题,接踵而
来的就是每个成员都会发现一大堆问题。

发现问题仅仅是第一步,我们还要在回顾会议中合理分析这些问题出现的原因、所属类别,并因此
划定问题的“责任田”。我们要明确这些问题是团队内部的,还是由于外在因素导致的,也就是说
要明确“责任田”的归属,指定处理人和处理时间。

此外,《敏捷回顾——让团队从优秀到卓越》一书的作者 Esther Derby 在接受 InfoQ 的采访时,


还提到:“使用回顾来解决冲突问题。他们在每个迭代中都进行检视、实验,并构建解决冲突的技
能和信心。随着时间推移,他们学会了如何处理每个团队都会发生的“平常的”冲突。当大家意见
出现严重分歧时,他们有能力、也有信心在团队内部解决问题。而且更有利的是,由于他们可以在
团队内部解决全部问题,他们的经理就可以花费更多的时间来消除组织中对团队造成的障碍。当然,
这使得团队能够轻装上阵,以更加轻松的心态来开发软件。”[2]

现在,在你的项目团队经历了一次艰难的迭代之后,你需要休息一下以等待灵魂的到来么?那么,
就请尝试一下敏捷回顾会议吧,或许你会从中得到意想不到的收获。

参考文献:
[1] Linda Rising,敏捷回顾活动“最高指导原则”答疑解惑
[2] Deborah Hartmann,图书节选:敏捷回顾——让团队从优秀到卓越

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷思考

解开最后期限的镣铐
张逸

最后期限(Deadline)是软件从业人员必须面临的最大困难与挑战,准确地说,它是所
有程序员包括项目管理者的可怕梦魇。当堂吉珂德看到郊野之上的数十架风车,风车的
翅翼如巨人的胳膊,正耀武扬威地奚落着这位中世纪后期没落的骑士时,堂吉珂德如勇
敢的斗士一般,跃马而上,用长枪狠狠地刺向风车,换来的却是长枪折断,人仰马翻,
最后大败而归。没错,最后期限之于程序员,正如风车之于堂吉珂德,确实是太强大以至于无法战
胜。

那么,我们真的要知其不可为而为之吗?就像孟子老夫子说的那般,虽千万人吾往矣!虽然充满了
风萧萧兮易水寒的悲壮,但铩羽而归的感觉,无疑会一次次挫败程序员的信心。更重要的是,IPO
变成了负值,投资方是否还能够将项目交付与你呢?

风车看起来是不可战胜的,但如果善于分析风车的关键,找到其“罩门”,也未始没有击破的可能。
例如,我们可以找到风车的枢纽部分,击破一点即可使其全线瓦解。有时候,最后期限真的是貌似
强大,但若能仔细分析,认真对待,也未尝不可突破壁垒,找到制胜之道。

我曾经参与过多个项目的开发和管理工作,坦白说,最后期限总是如内心的毒蛇一般盘绕,始终是
挥之不去的阴影。在客户的声声催促中,就像是听到了定时炸弹最后计时的“嘀嘀”声。明知炸弹
就要爆炸,自己却无能为力,这样的感觉令人沮丧。我的一个长处是善于从失败中挖掘教训,所谓
“亡羊补牢犹未晚”,即使这个项目失败了,至少在下一次项目中还存在成功的可能。总结下来,
大约有如下几点可以用来对付“最后期限”。

1、与客户协商最后期限。听起来是一个笑话,
如果最后期限可以协商,就不成其为最后期限
了。然而,固执的管理者们,为何要未战而退,
却不尝试一下可能会 出现的万分之一 机会
呢?当你充满绝然的勇气与神情去面对客户,
以专家的口吻斩钉截铁地说道:“没错,按照
您规定的最后期限,我们绝对能够完成您的要
求。只可惜我们却没有充分测试的时间。您是
否愿意给出测试的时间,这样我们就能够交付
一件让您绝对满意的高质量产品了。”或许你
得到的是断然的回绝,然而如果你能够合理有
效地与客户协商,仍有回旋妥协的余地。前提是,当你在向客户倒苦水的时候,千万不要说产品在

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

8
9

解开最后期限的镣铐

最后期限之前无法完成。因为这个最后期限往往是你的市场代表为了拿到项目而做出的一次妥协决
定。如果你否决了这一期限,会让客户怀疑你所在公司的诚意与能力。关键是质量!因为对于客户
而言,时间固然是一个决定因素,但高质量的产品才是最后的关键。尝试与客户协商,或许你会冲
破最后期限的壁垒,看到海阔天空,呼吸一口新鲜空气。

2、与客户协商功能要点。如果不能从时间上做文章,那么就另辟蹊径,在功能上夺回你失守的阵地
吧。功能总是有轻重之分的,将那些可有可无,或者是客户不太关注的功能砍去,就等同于你争取
到了更长的时间。想想那些已经投入使用的产品吧,例如微软的 Word。我们可以做一下调查,世界
上的所有 Word 用户,有多少人全部使用过 Word 的所有功能?或者我们从使用概率来分析,产品中
还有哪些使用概率不超过 30%的非关键功能点?找到这些功能点,打印一个清单,然后鼓动你的如
簧之舌去与客户谈判吧。或许,你能得到一个理想的结果。

不幸的是,你碰到了一个极其顽固的老古董客户,就像那些整日呆在办公室里无所事事,却有着鹰
隼一般锐利的目光,专以折磨人为乐的奴隶主监工一般。他们不会听取你的友好协商,而会像孩子
一般,关闭上两只耳朵,只是抱着手里的玩具使劲摇头,即使你递给他的玩具可能会更漂亮更好玩,
他仍然会固执地抓住自己的玩具死死不放。现实世界中,我们总会碰到这种情况,我们会在客户那
里碰得头破血流。那么,我们该怎么办?

让我们从开发过程中寻找答案吧。唐柳宗元道:“苛政猛于虎”,我们常常会畏“最后期限”如虎,
然而殊不知错误的开发过程或方法有时候会比这条“虎”更为凶猛!此外,我们还可以从计划执行、
任务安排、团队合作等诸多方面找到可以挤出来的时间。这个时候,项目管理者必须像葛朗台老头
一般,精打细算,珍惜每分钟项目时间的运用。然而,项目管理者绝对不能因为时间紧促,而像周
扒皮那样半夜学鸡叫,让团队成员加班加点,像牲口一般的对待。偶尔为之,或许无伤大雅,然而
每日都如此,那么只能说明这个项目已经到头了,赶紧准备失败的毒药吧。因此,我们要做到:

1、合理裁减开发过程。在项目管理过程中,必须执行相应的开发流程,例如计划评审、同行评审、
阶段评审等。此外,QA 会拿着众多检查点,每日走查项目组是否在质量保证方面存在缺陷。因此,
在项目周期紧张的情况之下,项目管理者与 QA 就必须针对项目的实际情况,合理地裁减开发过程,
省去一些不必要的官僚会议以及 QA 检查的表面文章。同时,随之而来的利益则是大量工件,尤其是
文档的减少。如果能够让开发人员能够从文山会海中解脱出来,谢天谢地,他会成为项目开发的急
先锋。要知道,世界上所有的程序员都在为文档的编撰而苦恼。减少文档不等于说不写文档,即使
是敏捷开发,注重代码与可工作的产品胜过完整的文档,仍然不会忽略基本文档的编写。虽然在对
需求和设计进行分析期间,我们可以考虑用面对面交谈的方式,或者在白板上写下我们的设计方案,
但为了项目沉淀或者产品维护与重构,以及考虑成员变化等种种因素,文档的编写仍然是项目开发
中不可缺失的重要环节。关键是编写文档的“度”。敏捷的布道者 Alistair Cockburn 在其书《敏
捷软件开发》中写道:“团队成员应当在为将来的使用而超支的成本与未来文档不足的风险之间进
行平衡。找到两者之间的平衡点是一门艺术……”我对那种形而上学的开发过程管理深恶痛绝。为
了通过阶段评审,我们必须要腾出时间来编写阶段评审文档,然后请来那些大多数尸位素餐的评审
委员会专家,然后不痛不痒地提出几个缺陷或错误,最后一团和气的结束会议。显然,这是一种官
僚思想,是集体的资源浪费。即使为了某些办公室政治考虑,那么项目经理也应该像牧羊犬那样,

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷思考

保护自己的团队成员免受这方面的干扰,就像 Scrum 所要求的那样。

2、合理的设定功能优先级,并以此制定开发计划。这里我们可以玩弄一个花招,即使我们无法在客
户那里寻求到裁减功能的支持,但我们仍然可以在功能优先级方面大作文章。例如将客户要求的优
先级高的功能,以及技术实现必须的高优先级功能先行实现,那么,到了最后期限来临之际,即使
我们还有一大堆低优先级的功能未曾实现,但由于客户最关心的功能点能够高质量地运行,最后的
产品虽然没有完全满足客户的需求,但凭借着优先级的合理划分,也可以让我们在后面的商务谈判
中占据先机。此外,我们还可以信誓旦旦地向客户承诺,我们会在交付产品之后,继续完成剩下的
功能。客户是否完全满意,谁知道呢?但至少我们交付了产品!以己之见,虽然这个产品不够全面,
但总比交付一个全面的产品,却错误频现要来得好。

3、提高会议效率。无论是传统的软件开发方法,还是敏捷方法,在软件开发过程中,不可避免要召
开各种各样的会议。毕竟软件是人开发的,而且是组成一个团队的人开发的,因而交流成为必然。
我并不反对召开这样的会议,相反,我很乐于参加这样的会议,因为这样可以让我的口才在全体同
僚面前得到充分地展示。然而,会有多少的宝贵时间淹没在这样的夸夸其谈,或者口沫横飞之中啊!
与其要求团队成员加班加点,还不如提高会议效率。我很认同 Scrum 对于会议时间的明确规定。例
如 Sprint 的计划会议保持在 4 个小时,而评审会议和回顾会议则保持在 2 个小时左右。至于 Scrum
的每日例会,更是短小精悍,只有 15 分钟。是谁发明了“站立会议”这个名词呢?发明者完全就是
一位天才!改传统的枯坐会议为站立会议,就可以收住那些夸夸其谈、口若悬河的家伙了。在我的
一个团队里,就有这样一个家伙,总是啰里啰唆,讲了半天也说不到重点。我每次看到他要发言,
我就有头晕的感觉,甚至有一种冲动,想用胶布封住他的嘴。不过,在我主持会议的时候,我常常
发现会议成员看我的眼神,有几分熟悉。会议之后仔细思考,发现他们看我的眼神,和我看那个家
伙的眼神竟然惊人的一致!Larry L. Constantine 在《人件集》的“因地制宜”篇中写道:“如果
想要团队工作获得最大成功,会议的主持人和记录者都应该以局外人的身份参加讨论会。……作为
整个团队的最高负责人,项目领导者应该积极参与团队中的讨论和工作,而不是对工作指手画
脚。……会议应该是在一种中立的气氛下进行。……另一方面,……陷入无休止的争论中,这时候,
最好由项目领导出面中止争论,暂时地放开当前的话题,或者很偶尔的,如果话题已经进入了死锁
状态,那么就由领导做出一个仲裁。”

4、合理安排人手。通常在我们面临最后期限的压力时,第一想到的是加班,
然后闪入脑海中的念头则是增加人手。加班策略素来为我所唾弃。以每人每
日的生产效率来看,虽然加班可以延长工作时间,但长期的过度疲劳必然会
降低生产效率,如此以时间换来低下的效率与团队成员的抱怨,完全得不偿
失。在长期积怨的情况之下,开发人员会产生一种破罐子破摔的思想,心里
认为反正都要加班,那么在正常上班情况下,反而会“磨洋工”,敷衍搪塞
项目经理安排的工作。那么增加人手呢?且不说这会增加项目成本,我们还
要考虑团队的新兵需要多长时间才能上战场?业务培训、团队磨合是新增成员必然存在的两大痼疾。
如果没有处理好这两个问题,不仅不能提高开发进度,反而会有拖慢或者打乱原有开发节奏的危险。
另外,如果添加的新手不幸是一个刺头或者“害群之马” 呢?需要明确的是,往往在项目经理提出
增加人手的情况下,项目经理并没有亲自挑选新成员的权利。这些新成员要么是闲置的,要么是其

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

10
11

解开最后期限的镣铐

他团队转过来的,要么是新招聘的。考虑前面两种情况,你觉得这样的成员能够达到及格乃至于优
秀的几率会有多大呢?如果是新招聘的,那么拜托,赶快在心里多念几遍“菩萨保佑”吧。总体而
言,如果项目经理没有挑选新成员的权利,最佳的选择是非到万不得已不要添加成员。所谓“万不
得已”,即是无论如何改进,如何协商,如何提高效率,都无法达成既定目标的情况。

兵贵在精而不在于多。关键在于知人善用,以及合理调度。一个项目经理在组建自己的团队时,必
须要了解自己成员的人格特点与技术特点。在理想状态下,如果项目经理具有挑选成员的权利,会
具有更大的成功率。如果项目过大,那么必须建立层级式的组织架构,而在划分出的各个小组中,
却应该以扁平的平等架构为最佳。这样就能够自由而不失于集中,平等而又不至于缺乏效力。当然,
具体的组织架构应依据企业文化、产品性质、开发规模、团队成员特点等各个因素综合考虑,不能
死搬硬套。在安排人手时,要注意对技能型人才和管理型人才的使用,注意对领域专家和系统架构
师的使用,注意对开发人员和测试人员的使用,注意对编档人员、 QA、配置管理员的使用。此外,
还需要养成从容不迫的心理,即使最终期限火烧眉毛,迫在眉睫,仍然要保证对架构的设计、对编
码的测试以及合理考虑产品性能、可用性和产品质量。

5、开发环境的保护与基础设施的维护。兵家云:天时、地利、人和。没有一个好的开发环境,很难
想象开发人员能够高效率的工作。开发环境必须是相对独立,又利于交流与沟通的工作室。具体的
说,项目组的工作环境必须拒绝项目无关人员的干扰与破坏,但却无阻于项目成员,特别是同一小
组成员的交流。此外,会议室的数量非常重要。我在管理一个项目时,竟然常常为寻找会议室而东
奔西走,将大量的时间浪费在会议准备上。此外,服务器、客户机、网络、打印机、白板、卡片,
以及开发工具和软件,例如 IDE 开发环境、版本控制工具、Bug 管理工具等,都需要在团队建立之
初就要准备好。对于计算机、网络和相关工具,则必须保证在项目开发期间的稳定性、畅通性。我
曾经在项目开发中,因为网络中断、病毒侵袭以及服务器坏掉从而破坏了 SVN 的版本管理等诸多突
发事件,让我在本来就紧张的开发时间里,牺牲了不低于三天的时间,真是让我抓狂不已!所以说,
一个好的网络管理中心、一个好的配置管理员,在关键时刻,可以抵得上半打高效的开发人员呢。
如果你在项目开发过程中,频繁遭遇这样的问题,我的忠告是,赶紧准备换一家公司吧。

6、合理控制需求变更。需求变更是软件开发必然遭遇的暴风雪,也是导致“没有银弹”的渊薮。传
统的瀑布开发模型在项目后期遭遇需求变更时,只能束手无策,但 RUP 与敏捷方法却能够坦然面对
需求的变更,Kent Beck 甚至在敏捷开发中提出了拥抱变化,真是足够勇敢与足够信心的宣言啊!
在软件开发中,若要应对软件开发,一般的做法是合理设计,以求系统与架构具有足够的可扩展性;
其次则是采用迭代的开发方式,通过定期甚至是短周期地交付可工作的产品,以印证需求与实现是
否一致。同时,在项目中通过引入客户的积极参与,使得项目组与客户的交流能够畅通无阻,从而
避免因为隔阂而导致需求分析产生的误差,以及需求变更无法及时提出。此外,利用原型快速开发
方式,可以尽快地交付一个无具体实现的产品框架或原型,以验证业务规则、业务流程以及客户对
GUI 的要求。然而,需求变更绝对不能无休止地进行,这会导致迭代的永无眠日。即使是敏捷开发,
我们仍然要设定客户委托事项的基本线,一旦超出这一基本线,变更委员会(CCB)或其他担负这一
职责的角色就必须提出异议,与客户协商或探讨这种变更是否是必须的。控制需求变更的一个实践
是,获得客户对分析出来的功能点的书面确认。虽然在发生变更时,客户的意见甚至可以无视这种
书面文章,但至少可以在与客户的谈判中抢得先机。根据 Mark Lines 所说,通过变更控制的增强还

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷思考

可以降低项目风险。确实如此,在与客户谈判中,我们要学会说出“拒绝”两个字。当然,在对需
求变更做出决定性意见之前,必须分析判断这样的变更是否合理,是否必要,或者优先级高。一种
折中的办法则是,欣然承诺此次变更,但需要延迟最后期限,或者放在下一次版本迭代之后。

7、预先评估风险。风险无处不在。Cockburn 将软件开发形容为攀岩或者穿越沼泽,已经充分说明
了软件开发过程中的风险。孙子兵法云:夫未战而庙算胜者,得算多也;未战而庙算不胜者,得算
少也。先预先着失败的可能,方能够谨慎地做好各种准备,考虑各种风险以及驱避措施,方能够最
大可能地取得胜利。软件开发的风险有很多,其中至关重要的是进度风险、技术风险、需求变更风
险、成员变动风险。

软件是可以度量的吗?看起来是,因为已经有了很多方法来完成软件的度量。从控制学的理论来看,
你无法控制那些你无法度量的。因而软件度量对于控制软件开发而言,就成为了关键。软件度量甚
至因此成为了一门学问。然而,我可以肯定地说,软件的度量不可能准确,尤其是对进度的把握而
言。即使一个项目的开发周期看起来是如此的充裕,以至于感受不到最后期限的压力,我们仍然要
对软件进度的控制采取如坐针毡的谨慎态度,即使这样在某些人的眼中,我成为了持怀疑论者,或
者悲观主义者,我仍然愿意背着这样的名身恶意地怀疑项目时间不够。原因有二。其一是我们的工
作量估算无法做到精确,即使是经验丰富的天才程序员,在估算项目的整体工作量时,都会出现偏
差。是的,我们采用了分而治之的方式,对功能进行分解,从最小单元来评估工作量。但我们无法
估算各个功能单元之间存在的各种显式和隐式关系,以及各种非功能性需求带给项目的影响。其二,
我们无法事先完全预知开发过程中的各种风险。我们得为这种风险买上一份保险,这样才不至于在
风险真正产生时要我们自己来买单,或者追悔莫及。

关于技术风险,最佳方式莫过于事先进行技术预演。不要揣测,或者从理论上去推导。在这个过程
中,我们可以应用经验,但最保险的方式还是对系统中的核心问题以及关键问题进行研究,创建技
术原型。它才是规避技术风险的定心丸。

成员变动风险是最难以预知的,因为人是最难以通过数据分析得出正确结论的动物。人的心理太复
杂了,因此在软件业中还专门诞生了“人件(Peopleware)”这门学问。在我们进行项目开发过程
中,谁知道有多少人会因为各种各样的因素,而萌生去意呢?此外,正所谓“人有旦夕祸福”,我
们总不能预测哪些成员会在开发过程中生病或者失恋吧?若要解决这个问题,一个办法是“结对编
程”。虽然提出这一方法的目的并不是为了应对成员变动的风险,但事实上这种互相协作的方式确
实能够将成员离开所造成的损失降到最低。以我的经验,要发生那种编程开发的一对都离开项目的
情形,实在是少之又少。还有一种办法则是 Constantine 提出的“交叉培训”。在其《人件集》的
《稳步提升的质量》一篇中,他提出“将交叉培训纳入项目的组织形式中,……是最有效、最有影
响的办法之一。这种方法同时也增加了工作透明度。通过增加团队中面对面工作的机会,团队成员
间自然也就增加了相互学习的机会”。此外,他还提出 “在团队中进行软件开发角色轮循,也为成
员增加了实践的机会,可以帮助大家掌握更多的技巧和知识。”这里固然在说培训,但它带来的结
果是让团队中各个成员都能够了解彼此的工作,这就能够弥补因为某些成员离开项目带来的空白。

这里同样牵扯出一个话题,就是关于团队的培训。我的理解是,即使最后期限泰山压顶,也千万不

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

12
13

解开最后期限的镣铐

要节省团队培训的时间,除非你的团队已经熟悉了项目开发的所有领域知识,以及解决领域问题的
所有技术知识,同时,这个团队已经固定不变的合作过三个项目以上,因而团队成员已经达到了一
个微小动作就能够心领神会的境界。有这样的团队么?或许有,不过我还没有看见。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷实践

在大型遗留系统基础上运作重构项目
熊节

本文以 ThoughtWorks 中国公司与客户合作的咨询项目为背景,为读者介绍如何在一个大型遗留


系统的基础上组织和运作重构项目,从而切实有效地改善系统质量。

现状

eMAN 是客户的一个核心业务平台。该产品采用了典型的 C/S 结构,负责处理大量请求和计算的后


台部分采用 C++开发,负责响应用户操作和处理业务逻辑的前台部分采用 Java 开发;此外该产品
还计划在新版本中提供基于 Web 的前台,这部分也采用 Java 开发。

在 ThoughtWorks 为该产品的开发团队提供咨询时,eMAN 产品已经发布了十多个版本,最新版


本代码量超过 40 万行,其中 15 万行是 Java 代码。一次又一次的赶工给它留下了大量的“技术债”:
系统缺乏测试,代码质量低劣,“copy & paste”的痕迹比比皆是,维护和新功能开发举步维艰。
我们这个咨询项目的主要目标之一就是为这个产品找出重构的办法。

原则

可以用两种不同的角度来看待一个软件:程序员的角度,商业的角度。

从程序员的角度看来,“成功的软件”意味着所有测试都通过、代码结构良好、并且容易理解和维
护。从商业的角度看来,“成功的软件”意味着它所创造的价值超出在它身上付出的代价。
1. 和别的任何 story 一样,重构的 story 类型的 story)
(以及其他“技术债” 也应该符合 INVEST
的标准。尤其是,它们的工作量应该得到估算,它们应该按照业务价值排列优先级。因为归根到底,
重构(以及其他任何开发任务)都归结为“花在代码上的成本”与“对业务创造的价值”之间的权
衡。
2. 按照定义,重构意味着“在不改变功能性行为的前提下改进代码的组织结构”。如果代码基
础本身脆弱而没有测试覆盖,重构的成本就会很高,因为你需要花很大力气来确认自己的修改没有
改变功能性行为。
3. 如果偿还“技术债”的成本非常高,那么与之对应的业务价值就必须更高,否则偿还这些债
务就将得不偿失。其结果是:一段代码,从程序员的角度看来越糟糕,从商业的角度来说就越不应
该去动它。
4. 综上所述,如果有人说“这一大堆代码都需要重构”,这样的说法很有可能是值得商榷的。
你需要把重构划分成细粒度的、可控的 story,为这些重构 story 制定验收标准,评估它们的优先
级,估算它们的工作量,然后逐一实现它们,并且放弃一些得不偿失的重构。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

14
15

在大型遗留系统基础上运作重构

在 eMAN 项目中,我们按照软件功能模块划分了 story,而不区分是新功能开发还是重构。比如说


一个典型的 story 可能是:
作为系统管理员我要从服务器列表中选择一个服务器从而让我可以登录到选中的服务器

不管是新开发还是重构,这个 story 的验收条件是一致的:功能通过验收测试,代码符合质量要求。


在实际工作中我们发现,对于同样的 story,新开发和重构的工作量差别不大。这也使得迭代计划
可以在 story 的基础上照常进行,不必特意区分重构和新功能开发。

持续集成

前面已经提到,重构 story 也同样应该是可验收的。除了确保其功能性行为仍然保持原样不变之外,


这类 story 还有更多的验收条件:单元测试覆盖率、代码复杂度、编码规范等指标都应该符合项目
要求。这些指标也同样适用于新功能开发的 story。这些指标的报告应该自动化地生成,及时地展
现给所有项目成员,为此我们需要一个持续集成环境。

eMAN 项目采用 CruiseControl 作为持续集成工具。每次开发者


往代码库中签入(check in)代码时,就会触发 CruiseControl
对项目进行构建,构建的内容包括编译、连接(对于后台部分)、
单元测试、测试覆盖率统计、代码复杂度统计、编码规范检查、部署到测试环境、功能测试等。由
于前台运行在 Windows 上而后台运行在 Linux 上,我们用两台持续集成服务器分别构建前台与后
台,然后再把两者集成起来进行功能测试。

在建立持续集成环境的过程中,我们发现 eMAN 项目以前缺乏有效的项目自动化(automation)


机制。虽然前后台分别有一些脚本用于执行编译、部署、启动应用等常规任务,但项目自动化机制
还有较大的欠缺:
1. 缺乏版本控制。原来的版本控制库中只有项目源代码和运行时配置文件,开发阶段的配置和
自动化脚本都不在版本控制中。
2. 环境依赖。原来的自动化脚本对操作系统、安装的软件环境甚至项目路径等因素都有依赖,
每个开发者从版本控制库获取代码之后还需要复杂的配置才能让系统在本地运行。
3. 自动化不彻底。原来的自动化脚本没有覆盖到构建的所有环节,并且各个环节之间没有连通,
开发者必须执行多个步骤的操作才能完成构建。

在这样的自动化脚本基础上,持续集成环境无法发挥出它最大的价值,因此我们对自动化脚本做了
一系列改进,达到的效果是:只要在一台干净的机器上安装 Java 和 Ant,然后从版本控制库签出
(check out)项目,在项目目录里执行 ant 即可完成整个构建。于是完整的构建不仅在持续集成
服务器上频繁运行,还在每个开发者的工作机器上更加频繁地运行。

在这个过程中我们用到的工具(前台部分)包括:

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷实践

* 持续集成服务器:CruiseControl
* 项目自动化工具:Ant
* 测试覆盖率和代码复杂度统计工具:Cobertura
* 代码风格监测工具:Checkstyle

安全网

正如前文提到的,对于重构性质的 story,测试覆盖率是一项重要的验收条件。这是由重构任务的
特性决定的。

重构(名词):对软件内部结构的一种调整,目的是在不改变“软件之可观察行为”
的前提下,提高其可理解性,降低其修改成本。

重构之前,首先检查自己是否有一套可靠的测试机制。

——Martin Fowler,《重构》

没有一套可靠的测试机制,重构就无从谈起,因为你根本就无从知道自己做的调整是否改变了“软
件之可观察行为”,甚至可能已经搞得系统不能运行还一无所知。而 eMAN 的现状正是如此:验收
测试无法自动运行,单元测试更是在上一个版本交付之后就再也没有运行过。简而言之,eMAN 目
前没有测试。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

16
17

在大型遗留系统基础上运作重构

对这种没有测试的系统进行重构,就像是编织一张网:先针对一小块功能编写验收测试,在这张“粗
网”的保护下再逐渐给代码添加单元测试,有了粗细两层网的保护再深入重构。随着重构的开展,
这张频繁自动化运行的安全网也渐渐铺开。从不断提升的测试覆盖率和不断降低的代码复杂度,我
们就能清晰地看到重构的进展情况。

为什么要首先编写验收测试?当然了,如果代码本身结构良好,单元(类、方法)之间关系清晰,
你也可以直接添加单元测试——但这样的代码基础就不需要大动干戈地专门组织重构了。我们在
eMAN 项目中发现,那些最需要重构的代码也是最难进行单元测试的,而没有测试我们又不敢动手
重构。(在“典型案例”一节我们将介绍几种阻碍单元测试的常见情况和解决办法。)这时验收测
试就可以在系统外围担任“看门人”,给我们一个起点:在调整代码结构以便单元测试时,我们至
少知道这些调整没有破坏系统的功能。

在 eMAN 项目中,我们用 Rational Functional


Tester(RFT)来做验收测试。我们还评估了另
一种针对 Swing 应用的功能测试工具 Abbot。相
比之下,RFT 最大的优势在于独立性:测试工具
与被测应用在不同的 Java 虚拟机里运行;而
Abbot 则是在当前虚拟机环境下运行被测应用,
如果被测应用与 Abbot 引用同样的第三方包,就
可能出现版本冲突。但 RFT 也有一些明显的劣势:
测试案例编写难度大,占用系统资源多,与 Ant
集成不佳,而且价格不菲。读者应该根据自己项
目的情况谨慎选择。

除了用 RFT 实现前台 Swing 应用的验收测试之


外,我们还用 Selenium 实现了前台 Web 应用
的验收测试。对 Abbot 的研究也没有浪费,我们
用它来实现了 Swing 界面的单元测试。从理论上
来说,任何一段代码都可以并且应该被测试,但
适当的工具能让测试事半功倍。组合多种测试工
具,从不同层面、不同角度对系统进行测试,才
能织起一张可靠的安全网。

与通常说的“测试驱动开发”(TDD)相比,这种重构项目的节奏略有些不同:不是标准的“红-

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷实践

绿-重构”,而经常是“绿-重构-绿”。不过,这两种节奏都是敏捷项目中很常见的,左侧的图就同
时包含了两者。

值得一提的是,图中的弧线不仅代表开发中的一项活动、系统状态的一次变迁,而且还代表一次在
结对中转移键盘的机会。在 eMAN 项目中,我们经常以这样的方式工作:一个人给现有代码补上一
段测试,把键盘推给身边的同伴,后者重构被刚才的测试覆盖的那段代码。以这样的节奏稳步前进,
确保了知识能够在结对的过程中得到传递。

典型案例

下面列举了一些 eMAN 系统(前台部分)中较常见的代码质量问题。我们没有列出一些更常见的“坏


味道”(例如大类、长方法等),因为 Martin Fowler 在《重构》一书中已经把它们描述得足够清
楚,而且针对它们的重构也相对容易。本文中列出的是一些相对规模较大、较为复杂的情形。

我们相信类似的情形也存在于很多其他系统中,但我们并不打算宣称这个列表足以包治百病。大规
模重构是一件极其复杂的细致活,很多时候你需要根据当前情况寻找适合自己的解决方案。

无法在测试环境中创建被测对象

对象在创建过程中自己尝试获得所需的依赖对象,就可能在单元测试环境下因无法创建依赖对象而
导致被测对象的创建失败,从而不能把被测对象放进单元测试。例如 AuthorizationService 类的
创建过程如下:

private static AuthorizationService instance = null;


private SecurityRightManager rightManager = null;

private AuthorizationService()
{
rightManager = SecurityBaseModule.
getSecuityModuleRef().
getSecurityRightManager();
}

public static AuthorizationService getInstance()


{
if(null == instance){
instance = new AuthorizationService();
}
return instance;
}

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

18
19

在大型遗留系统基础上运作重构

在单元测试环境下 SecurityBaseModule.getSecuityModuleRef()返回 null,因此尝试创建


AuthorizationService 会抛出 NullPointerException 异常。

重构办法:
1. 通过构造函数的参数传入依赖对象,而不自己创建。
2. 使用被测对象的地方负责创建依赖对象。随着重构进行把这一责任不断上推。
3. 把初始化动作与构造函数分开,构造函数只用于获得依赖关系。如果还需要更复杂的初始化
动作,在单独的初始化函数中进行。

重构目标:
通过调用构造函数、传入 null 作为依赖对象引用,能在单元测试中创建被测对象。

无法在测试环境中运行被测方法

如果被测方法在计算过程中自己尝试获得所需的依赖对象,就可能在单元测试环境下因无法满足依
赖对象的要求而导致测试失败,从而无法对希望测试的方法进行单元测试。例如
SessionManager.isAdminGroupUser 方法如下:
public boolean isAdminGroupUser(String userName)
{
try {
boolean result = RpcInvoker.isAdminGroupUser(userName);
return result;
} catch(Exception ex) {
DebugTracer.trace(ex);
return false;
}
}

在单元测试环境下 RpcInvoker 的调用尝试必定会抛出异常,于是对 isAdminGroupUser 方法的


调用必定会返回 false。如果让 RpcInvoker 通过网络进行真实的 RPC 调用,不仅工作量大,使测
试不可靠,而且这样的测试实际上主要是在测 RpcInvoker 的工作是否正确,变成了集成测试而不
是 SessionManager 的单元测试。

重构办法:
1. 通过构造函数的参数传入依赖对象并保存在成员变量中,需要使用依赖对象时通过成员变量
调用。被测方法不直接创建依赖对象。
2. 使用被测对象的地方负责创建依赖对象。随着重构进行把这一责任不断上推。
3. 单元测试中用 mock 框架(推荐 JMock)创建依赖对象。在每个测试案例(即测试方法)中
独立设置对 mock 对象的期望,发现明显的重复时再抽取公共代码。

重构目标:

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷实践

1. 通过调用构造函数、传入 mock 对象,能在单元测试中创建被测对象。


2. 对 mock 对象设置适当的期望,能调用被测方法,并覆盖到正常和异常路径。

不恰当的对象获取方式

不必要的 Singleton 模式。如果一个对象本身没有内部状态,只是根据外界状态进行计算,这样的


对象不需要是 Singleton 的。例如 CommonTool 类和 SessionService 类:这两个类只是把请求
转发给其他对象处理,它们不需要使用 Singleton 模式。

通过其他对象获取。从其它对象中取出自己需要的依赖对象,“由谁提供某个对象”的决策相当随
机,有时通过一条长链来获得自己真正需要的依赖对象。例如要得到 RightPaneXMLParser 对象,
就需要通过下列方式:
SecurityModule.
getSecuityModuleRef().
getSecurityConfig().
getTableXMLParser()

重构办法:
1. 如前所述,对象所需的依赖对象全部以构造函数参数的形式获得,将“创建对象”的责任不
断上推,直至系统顶端的某个位置聚集了系统中绝大部分的对象创建逻辑。
2. 在系统顶端分离出一个全局工厂对象,该对象负责创建系统中所有的对象,并组装对象之间
的依赖关系。其他地方原则上不作对象创建。
3. 在少数不直接被这个系统顶端调用的地方(例如对外暴露给第三方的接口),从全局工厂请
求自己需要的对象。
4. 引入轻量级 IoC 容器(建议在 PicoContainer 和 Spring Core 之间选择),替代这个全局
工厂对象。

重构目标:
1. 系统中主要业务对象的创建都在全局工厂进行。
2. 业务对象是否 Singleton 能够以配置的方式管理。

对象依赖关系复杂

通过上述重构,尤其是改为通过构造函数参数来获得依赖对象之后,在一段时期会发现各个对象的
构造函数堆积了大量的参数。例如经过重构之后的 UserLoginDlgHelper 的构造函数签名如下:
public UserLoginDlgHelper(LoginInfoComp loginInfoComp,
SessionManager sessionManager,
LoginMgr loginManager,
ResCenter resCenter,
GuiUtil guiUtil)

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

20
21

在大型遗留系统基础上运作重构

共有 5 个依赖对象从构造函数传入,显得构造函数相当臃肿。这实际上反映出对象本身的臃肿:这
个对象承担了太多责任,因此需要依赖大量其它对象。过长的构造函数参数列表是一个直观的指标,
让我们能够清晰地看到对象依赖的情况。经过观察可以发现,这些依赖对象都分别只在几个方法中
被用到,这意味着 UserLoginDlgHelper 对象本身的职责可以分为几个相对独立的方面,可以被拆
分为几个独立的对象。

重构办法:
1. 以依赖对象的使用情况为线索,把大对象拆分成多个小对象。大对象中原有的方法不删除,
而是把调用委派给小对象。
2. 逐一修改大对象的使用者,让它们使用拆分出来的小对象。
3. 大对象中的方法无人使用时即可删除,依赖对象无人使用时即可从构造函数参数列表中删除。

重构目标:
每个对象的依赖对象数比较合理。

数据缺乏对象封装

系统中一些数据以原生类型或者简单容器的形式呈现,“对数据的操作”与数据本身脱离,散落在
系统各处。这样做的坏处是不容易看清数据代表的含义和处理这些数据的逻辑。特别是在数据结构
发生改变时,必须找出所有操作这些数据的地方,同时修改,一旦有遗漏就很容易引入隐晦的错误。

例如“登录信息”在系统中用 Vector<String>数据类型表示,其中各个字符串分别代表 DS 服务
器 IP、DS 服务器端口、认证模式、安全模式等信息。“共有多少个字符串”和“各个位置上的字
符串表示什么含义”等信息在 ServerInfoTable 类中以常量的形式保存:
public static class ServerInfoTable
{
public static int ColumnCount = 6;
public static int IPColumn = 0;
public static int PortColumn = 1;
public static int SecurityModeColumn = 2;
public static int LastLoginUserNameColumn = 3;
public static int IsLastLoginServerColumn = 4;
public static int AuthModeColumn = 5;

系统中共有 5 个类、十多个地方引用这些常量,还有一些地方直接用魔法数来访问这些信息。如果
新增一列数据,需要修改的地方很多,很容易出错。

重构办法:
1. 创建类型来封装原生数据,同时提供对象与原生数据之间的双向转换方法。
2. 找出原生数据的“索引常量”(如前面的 ServerInfoTable),以它为线索,逐个方法进行
重构,把其中使用原生数据的逻辑改为使用新对象。在方法入口处把原生数据转换为对象,出口处

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷实践

把对象转换回原生数据。
3. 一条调用链重构完成后,即可修改其中各个方法的签名,由传递原生数据改为传递对象。
4. 如果某些操作不涉及其他业务对象,只是操作这组数据,把这样的操作移到新建的类中。
5. 在调用链附近留意检查是否有不通过“索引常量”而用魔法数直接访问数据结构的情况。

重构目标:
1. 与特定数据结构相关的描述和操作都封装在对象内部,“索引常量”和魔法数被删除。
2. 改变数据结构只需要一处修改。

小结

大规模遗留系统的重构一直是困扰众多软件组织的难题。所谓“冰冻三尺非一日之寒”,大型系统
中的质量问题是经年累月堆积下来的,要解决这些问题也只能从价值最高的地方入手,耐着性子一
点点重新恢复代码质量。经过这个咨询项目,eMAN 产品开发团队在原有代码的基础上划分了细粒
度的重构 story,建立了持续集成环境,并按照共同探索出的节奏,以测试为驱动、以代码质量为
导向,不断重构以改进代码质量,并且积累了一些常见问题的解决办法,为大规模遗留系统的重构
找出了一条切实可行的路子。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

22
23

单元测试实践小结

单元测试实践小结
林仪明

一、单元测试的好处

在系统开发过程种使用单元测试,会带来很多的的好处,最明显为:
When you become convinced of the value of comprehensive unit testing, you’ll find that
it begins to influence how you write code, and the frameworks you choose to use。

此外,单元测试可以有效的支持重构;同时其本身就是一个很好的类使用说明;

二、单元测试的关注点

应用单元测试,首先要解决的是单元测试的关注点。

测试的关注点在于测试逻辑,只要有逻辑就要写测试代码。测试的手段就是验证所有被测试方法的
所有产出物,包括:
1. 测试方法的返回值
2. 测试方法的执行流程

例如:
public class DomainService {
private static TheDAO dao = new TheDAO ();
public ReturnObject findByCond(String) {
return (ReturnObject)dao.getBeanByCondition
("select * from ReturnObject where cond="+ paramter, ReturnObject.class);
}
}

在对于测试 findByCond 方法,有两个测试用例:


A. 测 传 递 给 TheDAO.getBeanByCondition 的 参 数 的 正 确 性 , 如 果 参 数 不 是 ” select * from
ReturnObject where cond=?”和 ReturnObject.class 则返回为 null。
B.测返回对象的正确性。

特别是第二点,在商业应用上比较常见的。通常有些方法无明显 output,通常是执行写表操作的。
对于这样的方法就是测试它的执行流程。当然这些方法本身包含逻辑的。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷实践

一个简单的解决方法是利用 Access Log 来实现(虽然这样的测试不多,而写的 case 代码也看着怪怪


的)。
public class ServiceExample{
private DatabaseDao1 dao1;
private DatabaseDao2 dao2;

public void noOutputMethod(){


if(...)
dao1.update(...);
if(...)
dao2.delete();
}
}

相关的测试代码可以这样:
public class MockDatabaseDao1 implements DatabaseDao1 {
private Map map;
public void setMap(Map map){
this.map = map;
}

public void update(args){


map.put("MockDatabaseDao1.update", args);
}
}

public class MockDatabaseDao2 implements DatabaseDao2 {


private Map map;

public void setMap(Map map){


this.map = map;
}

public void delete(args){


map.put("MockDatabaseDao2.delete", args);
}
}

public class ServiceExampleTestCase{


private Map map = new HashMap();

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

24
25

单元测试实践小结

public void testNoOutputMethod(){


DaoTest test = new DaoTest();
DatabaseDao1 dao1 = new MockDatabaseDao1();
dao1.setMap(map);
dao2.setMap(map);
DatabaseDao2 dao2 = new MockDatabaseDao2();
test.setDao1(dao1);
test.setDao2(dao2);
test.noOutputMethod();
assertEquals(new Boolean(true),
new Boolean(map.containsKey("MockDatabaseDao1.update")));
assertEquals(new Boolean(true),
new Boolean(map.containsKey("MockDatabaseDao2.delete")));
}
}

例子只测试执行流程,实际实践中还可以验证所有的参数。

我们还可以考虑利用 AOP(dynamic proxy 和 cglib)结合 StackFrame 来改进这个测试方法。这样不


用每次做同样的工作。开源的 EasyMock 已经很好的实现流程点以及参数验证测试.

三、单元测试的手段

讨论完测试的关注点后,看看单元测试的手段:编写 Mock object(Stubs)。

编写 mock object 来辅助测试,是非常重要的技术。Mock object 分动态 mock 和静态 mock 即 stubs。


推荐采用动态 mock,因为动态 mock 方式中,mock 数据和测试用例在一起,非常方便开发,阅读和
管理,而采用 stub 则不得不编写较多的类用于处理不同的测试用例。

而从目标来看,Mock 包括接口和类的 mock。接口的 Mock 很容易,而具体类的 mock 也很简单,通常


利用子类继承的方式实现;利用 cglib 框架可以很好大达到测试目的。

针对于 POJO 的设计模型,采用 EasyMock 可以很好的实现动态的接口和类 mock。

现在看看实际面临的具体困难:
职责不明确
类或类方法的职责不明确,违反 SRP 原则.一个类或方法处理了本不该有它处理的逻辑,使得单
元测试需要关心过多的外部关联类。

静态方法

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷实践

静态方法使得调用者直接面对实际的服务类,难以通过其他方式替换其实现,也难以扩展。

无返回值的服务方法
无返回值方法使得调用者难以模拟其实现

直接访问对象实例
调用者直接实例化服务对象,从而使用服务对象提供的服务.同静态方法一样,直接面对其服
务类。

J2se 和 J2ee 标准库或者其他类库


标准类库中有非常多的接口调用使得调用者难以测试 e.g JNDI, JavaMail, JAXP。

准备数据及其困难
编写测试用例需要外部准备大量的数据

针对这些困难,可用解决方法如下:
针对于职责不明确
1. 重构系统。对于职责不明确的代码,只有通过重构才可以达到单元测试的目的。
2. 自我测试。针对于 class 的测试,使用自代理测试模式, 使得测试时,可以重写被测试类的一些
方法.达到测试的目的.通过 extend class override methods 来实现。Inner class mock 方法也
一样。不过这种方法比较别扭。

针对于静态方法
静态方法的 mock。静态方法由于是直接面对服务对象,比较麻烦。不过,并非不可以测试,有 3 种
方法。
1)如果有源代码,应更新设计,通过委派给内部单例的做法,提供静态服务,这样就允许设计上对
于内部单例实现的代理和 Mock。
2)利用 classpath 的特点来实现。方法很简单,mock 类与建立一个将被 mock 的类的 package,class
name 以及方法签名完全一样,但方法实现却是 mock 过的。在运行测试用例时,把 mock 类打成 jar
(不一定要这么做),在配置 classpath 时确保,该 jar 的位置在当前 class 之前,就可以实现替
换。
3)利用 AOP,Amock 类库采用了 AspectJ 提供的类加载时字节码增强能力,可以提供对无源代码的
静态方法 mock。

针对于无返回值的服务方法
EasyMock 在 2.2 版本上支持 StubAnswer,使得我们可以模拟该方法;

针对于直接访问对象实例
重构系统。采用接口隔离的设计方法。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

26
27

单元测试实践小结

针对于 J2SE 和 J2EE 标准库


1.重构系统。采用 POJO 的设计方法
2.使用成熟单元测试框架

除了最基本的 Junit 外,Opensource 提供了很多非常有价值的单元测试框架,熟练使用这些工具,


可以提高测试的效率。包括对准备大量的数据,以及 j2ee 的框架代码。

现有代码的可选自动化测试工具:
1. EJB: MockEJB 或者 MockRunner
2. Servlet:Cactus
3. Struts:StrutsUnitTest
4. XML:XMLUnit
5. J2EE: MockRunner
6. GUI: JFCUnit, Marathor

针对于准备数据及其困难
1. Data Object:DDTUnit。准备大量数据。
2. Dao:DBUnit。初始化数据库。批量产生数据库数据。

此外,Spring 框架本身可以用来组织大量而复杂的测试对象模型,只要配合好一定的设计手法和约
定,大量数据和对象的组织问题就可迎刃而解。

四、分层架构下的单元测试
1 Web 层的单元测试
主要测试 Controller 的数据结构化逻辑。如果 View 是利用模板引擎的,需要测试页面的控制脚本
是否正确。
2 Domain Service 的单元测试
包括了业务规则和业务流程,参见下表:
有四种参与对象:
参与对象
1. Domain Object2. Dao 对象 3. 其它 Service 服务。4. 工具类
产出物 1. 返回值包括 POJO,和结构化的数据(如 XML)2. 传递给流程节点的参数值
概念上,业务逻辑和业务流程是相对独立的。实际代码中,虽然一些业务逻辑是
相对独立的。但是有一些业务逻辑与流程合在一起。由于业务逻辑有明确的返回
特点 值,业务规则可以独立成一个方法,其是有显示的返回值,这样单元测试就可以
关注在业务规则的测试上。而业务流程通常没有显示的返回值,在很多实践中表
现为持久化动作,测试比较麻烦,需要重构和通过 EasyMock 来完成测试。
返回值包括 POJO,或者结构化的数据如 XML 可以利用 XMLUnit 来解决。流程节点
测试覆盖面 的访问,以及传递给流程节点的参数值。即对业务流程的测试,可以使用上面的
访问点的方法。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷实践

3.Dao 的单元测试
第一个面临的问题是:做 Dao 数据访问层的单元测试时机。another word 也就是要不要做单元测试。

以下几种情况是不用测试的:
1. 如果 Dao 就是简单的 CRUD,那么不用测;在未来当我们使用 1.5 的范型后,这些 CRUD 只要在父
类做一边里就可以了。
2. 如果 hbm 文件是自动生成的,那也不用测。

以下是要测的情况:
1. 如果 hbm 文件是手工写的,那么需要你保证 hbm 的正确性。如何测试,后面再说。
2. 如果 Dao 中包括了一些组合查询,那么这是一种逻辑,就应该去测;如果 Dao 的查询还包含了某
个排序机制,这个排序逻辑依据的是业务字段,那么也是要测的。(理由是:这些逻辑可以在 java
代码实现,不过是性能太差了,但是既然 java 代码的逻辑要测,那么我们没有理由不去测在 sql 中
的逻辑)。

第二个问题如何测试:
0. 测试数据准备
可以将 BA 准备的数据导出。在利用 Excel 编辑产生一批数据。

但是每个 UnitTest 测试本身应该 focus 一个关注点上,所以每个 UnitTest 的数据保持在较少的水


平上。

另外由于 DBUnit 导入数据的顺序是依据 sheet 的顺序的,请注意把所有外键表在前,否则插入数据


时,会报外键不存在错误。

1. 数据库的选择
a.可以直接用小组用的开发数据库。优点:现成的,所有 schema 都建好了。缺点:目前数据库的数
据干净性无法保证,连接速度太慢。
b. 使用 hsqldb。优点:利用其内存模式,可以随测试程序启动,简单小巧,schema 可以自行定义,
每人各自一套互不影响。缺点:无法提供 PLSQL 支持。出于 UnitTest 本身的要求,以及性能上考量,
大部分情况下,建议使用 hsqldb,对于涉及到 PLSQL 的,需要 mock 处理。

2.测试 hbm
利用 hsqldb 内存数据库,在 setup 的时候,利用 hibernate 的 SchemaExport 工具类,将 hbm 导出
成数据库的 schema,如果有确实有潜在问题,那么测试程序将不通过。

3.测试 Dao
很简单了,调用 dao 程序操作。对于 save,update 和 delete 操作的。需要利用原始的 connection
执行查询验证。对于组合查询的和逻辑排序的,就是一般的做法了。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

28
29

单元测试实践小结

4. 在使用 DBUnit 时,测试非只读操作时,我们经常会采用 DatabaseOperation.CLEAN_INSERT 策略.


在关联表比较多时,效率会很差.因为每次 setUp,tearDown 时都会重新先 Delete,再 Insert 所有的
数据.另外,我们还有一种数据库操作测试的策略,就是使用真实数据库,在每次操作完毕后都回滚事
务。

五、单元测试的考量

最后要说的是单元测试的度量和考核。
测试覆盖率,简单的说就是衡量测试活动覆盖产品代码的指标。测试的目的,是为了验证产品代码按
照预期的目的执行,也可以被看作代码功能的文档说明。进一步的分析,测试覆盖率间接的衡量产
品的质量,因为它只是衡量了测试代码的质量,而不是产品代码。

常用测试覆盖率衡量指标:
Line coverage
Basic block coverage
Method coverage
Class coverage
Branch coverage

并非覆盖率越高越好,实际针对于不同类型的公司,所需要的单元测试覆盖率也不同,必须考量单
元测试的投入产出。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷实践

异地分布式敏捷软件开发
李默

异地分布式软件开发(Distributed Software Development)是指由多个位于不同地理位置的团队


进行同一个软件项目的开发过程。这个词越来越频繁的出现在各种技术媒体中。

异地分布式软件开发不同于外包,它建立在平等关系的两个团队之间。通常是一个公司的不同分公
司或办公室间的协作,他们之间大多不存在博弈的合同关系。而外包是指一个公司将其软件系统的
开发委托给另一个公司或组织完成。二者之间是合同的甲乙方关系。

但无论是异地分布式软件开发或是外包,可以接触到实际客户的一端一般称为 on-site,另一端可
相应的称为 off-site,他们可以根据地理位置分为三类:on-shore(在岸,指在同一个国家或同一
个时区内),near-Shore(近岸,在接近的国家和地区中)和 off-Shore(离岸,通常在时差 8 小
时以上)。如下表:
offsite on shore near shore off shore
Distributed 北京办公室– 印度分公司- 中国分 硅谷总公司 - 中国
Development 西安办公室之间 公司 或印度分公司
Outsourcing 北京某公司– 东京某公司 - 大连 欧洲某公司 - 中国
Development 广州另一公司 另一公司 另一公司

异地分布式开发的组织方式

异地分布开发的组织方式有很多种。最常见的一种是公司将完整的团队组织结构分布在两地,每个
团队都有本地项目经理,需求分析师,开发者以及测试。同时公司设定项目总负责人角色,负责两
地的沟通与协调。参见图 1。

有的公司将需求分析人员放在 on-site 一端,开发者、测试人员和项目经理在 off-site 一方,同时


在本地也保持常规的需求分析师。也有公司将测试人员和开发人员分放在不同地方,一方面开发,
另一方面利用时差,在夜间测试并在第二天及时反馈测试结果。参见图 2。

各种组织方式都有其不同的适用场合。然而他们的共同点在于,都是注重 micro-management,即加
强在本地团队中项目管理和协调,而不是由一个人同时直接管理两地的活动。同时,也尽量保证团
队两边都具有项目协调人、本地项目经理、需求分析师等辅助角色。

基本原则:极尽交流之能事

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

30
31

异地分布式敏捷软件开发

异地分布软件开发面临的最大问题是交流问题。随着人员距离的增加,交流效率将大大降低(参见
Alistair Cockburn 的文章),同时交流成本将极大提高。很多时候 on-site 一端团队不能把正确
的需求传递到 off-site 一端,这直接造成产品质量的下降。

图1

为了使避免这种情况,应尽量采用一切手段来提高交流的效果。例如,项目经理和团队成员都需要
了解其他人的工作状态,一个技巧是可以将你的 MSN 或 Y!名称后缀写上你在做哪一块的需求。并可
以随时和同事通过 IM 进行交流。

图2

每天的定时会议将成为很重要的一个很重要的交流方式。如果团队的人数较少,大家可以按照站立
会议的方式在电话会议系统中说明自己的情况和遇到的问题。如果人数较多,一种可替代的方式是
每个团队自己进行每日例会,并由个项目的项目经理和需求分析人员进行另外的会议以便协调工作。

如果两个团队时差较大,例如中国北京时间和美国东部时间时差 12-3 小时,想要进行直接的电话会

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷实践

议交流很困难。如果遇到 3 个处于不同时区的团队,更是经常不可能找到一个合适的时间来进行任
何的会议。在国际化的公司中,起早贪黑的进行几地的电话会议很常见,但这却不适用于整个开发
团队。对这种情况,每日的开发状态邮件是很有用的。每日开发结束后由项目经理或成员来根据团
队的情况来撰写一天的总结,并发送给远端的团队。

图 3 交流频度和价值图,Vincent Massol,2004

交流的障碍经常发生在陌生人之中,如果两地的开发人员互不熟悉,可以考虑将双方人员的照片贴
在墙上,以增加熟悉感。可行的话,进行可视会议和当面的会谈。尽量减少陌生感,使交流效果提
升。

任何交流方式都比不上面对面的交流。异地开发时,off-site 一端很容易丢失 on-site 一端与客户


交流的语义上下文和环境。如果情况允许,公司应该设立常规的出差和轮换制度。让一部分的团队
成员到另一端,见一见一起工作的同事,了解一下客户的需求和感受一下不同的环境。

敏捷开发过程的改进

一般的敏捷过程中,都会有一个初始阶段,在这个阶段了解开发需求和制定发布计划。要进行这样
的活动,最理想的办法是让所有人都出差到 on-site 一端,一起了解需求和建立共识。这将会对后
面的开发有很大帮助。如果由于人数或成本不可行,至少要派遣所有的需求分析师和项目经理、协
调人以及部分测试人员到场参与。对于迭代一级的计划,应该由两地的项目经理和需求分析师提前
进行计划会议并做出决定。

日常的项目管理工作中,采用卡片墙的方式只适用 in-house 的开发。在异地开发中,为了使得每个


团队都可以了解到团队任务,至少需要在两边开发室都设立卡片墙,并保持同步。可以采用在线工
具帮助进行项目跟踪,例如 Mingle 或 Trac,都是适用的在线工具,同时也是在线 Wiki 或共享知识
库。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

32
33

异地分布式敏捷软件开发

项目协调人,应当制定完善的交流计划和交流机制。例如前文提到的每日的例会和每日开发状态邮
件,每周的需求交流计划,问题的提出和反应机制等等。这些应当制定成为团队守则来遵循,并随
着实际情况的变化修订。交流不怕多,只怕不充分。

一个共享的代码版本控制系统是必须的。例如在公司内网建立一个 SVN 并通过 VPN 来使用。On-site


和 off-site 团队可建立自己单独的持续集成环境,但需要保持系统环境的一致。两方的开发人员都
应该保证每日离开办公室前的提交通过集成。这样可以避免异地团队开始开发不至于被失败的集成
所耽搁。

基本的敏捷时间必不可缺,例如测试,尤其是功能测试。On-site 的 QA 应当在需求确定的时候制定
好验收条件。一个描写良好的验收条件会对开发人员有所帮助。尤其是在 On-site 一端不能及时解
答问题的时候,会起到很大的作用。

每个迭代结束时,应尽量安排一个两地同步的演示会议。让所有人都在电话会议上看到这个迭代的
成果。迭代后的总结与回顾也应当两地一起进行,如果人数和条件不允许,可以分别进行,并互相
通报回顾结果和改进方法。

离岸团队的参与度

多团队中,处于 on-site 的成员由于可以接触到客户,他们的话语权可能会被放大,使得 on-site


一边的人倾向于命令式的消息传递,直接指派需求和开发进度,而忽视了对需求背景情况和上下文
进行介绍。这种情况可能造成 off-site 一端团队产生抵触心里,从而导致项目的失败。

解决方法是提高 off-site 团队的参与度。如制度性的进行人员轮换,让两端的团队成员有所接触,


并互相熟识。定期组织两个团队的共同活动。如果都处于一个时区,可以考虑进行每周的 Learning
Lunch,大家在互相能看到视频的情况下一起吃饭和听讲座。讲座内容可以是任何话题,例如一些项
目相关的技术决策等等。

不要忽视 off-site 团队的任何意见和建议,他们在很多时候能从另一个侧面对项目提出见解。鼓励


off-site 团队决策和发起讨论,这样可以提高他们的参与度。

实施异地开发的最初目的是为了降低人力成本和运营成本,一些跨时区的异地开发还可以提高时间
利用效率,实现全球 24 小时开发。然而,异地开发带来了高昂的交流和管理成本,如果处理不当将
直接导致项目或产品的失败。

近年来随着国内软件公司业务的发展,异地开发项目将会越来越多。全球化的进程也会使得外国公
司开展更多类似的开发。异地开发项目将会逐渐发展和普遍。可以想像,多年以后,如果一个公司
没有异地开发的团队,将会是多么的令人诧异。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷方法

McDonald & Scrum


徐毅

几天前去麦当劳的时候,见到一个领班模样的人在训斥一线员工,没听清楚具体是什么事情,就是
觉得这样的一种管理模式或者说反馈模式非常的好,快速、有效、有针对性,然后由此联想到 Scrum。

Scrum 是一种敏捷方法,强调快速反应,讲求人的配合等等。而其团队组织方式是多功能型,由具
有各种才能的人组成足以达成既定任务的团队。有清晰的工作目标,有一定的规范辅助,留给个人
有一定的发挥空间,成员之间彼此协作,而 scrum master 则担当着现场督导的职责,确保团队能够
获得完成任务所需的必要资源,以及遇到的障碍。

我所见到的麦当劳工作方式。前线的接待员会收集顾客
需求,然后获取相应的食物,交付顾客,收费,完成一
次顾客服务过程。而在这其中,接待员通过收银机确定
食物选择后,其数据被及时交互到后台操作间,操作间
基于此数据准备食物,并放入中间的食物通道,而食物
通道有不同的编号,以分发不同类型或同类但口味等有
变化的食物。由于整个过程的快速循环,on-shelf 的食
物流保持在相当低的程度,减少了因为食物变冷等造成
的损失。当有接待员在获取食物时,其他人若有相同需
求,可以直接通过话语告诉对方多获取一份,这可以看做是一种团队协作,而它可以减少服务所需
时间。领班或其他的店堂人员会在整个店内巡视,及时发现需要加强或立即服务的事件,并控制整
个局面,如二楼未整理的餐桌过多,阻碍了顾客就餐,或是有服务员的服务操作不规范等。

有时候进入夜间服务时间段时,其模式似乎有一定的变化。食物的准备没有冗余备份机制,而是完
全依赖订单,当接待员下单后,先确定的单子首先获得食物,后下单则后获得,后方的操作台完全
是一种 on-demand 的方式,由销售终端拉动。

而今天在上海逛正大广场的时候,看到那里的麦当劳又有一些不同,其接待员压根就不回头,专业
的订餐员,有专门的人会获取准备好的食物。似乎是一种每个流程部分都有专门的人员在负责,而
并非我在杭州常去的那个餐厅的接待员一样有更多的任务。我想这可能和餐厅的人流量有关,正大
广场的人流量貌似比较大,如果接待员下单,然后获取食物,递交给顾客,那么下一位顾客等待被
服务的时间就有些过于漫长。而有专人负责配餐,并送抵柜台的话,那么在接待员下好单开始服务
第二位顾客的时间段里,配餐员也可以完成操作,配餐完毕,第一位顾客就可以得到食物了。更有
助于快速的服务顾客,和较快周转。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

34
35

McDonald & Scrum

那么这和 scrum 有什么关系呢?恩,也许没关系,也许有。scrum 模式中的一些关键的工作方式和


麦当劳体现出的这种模式有许多相同点。首先,团队是多功能型的,可以是多功能型的个体成员组
成团队,也可以是不同专职的个体组成一个整体多功能型的团队。二,团队互相帮助,有共同的目
标,如使顾客在设定时间内完成订餐过程,如在一个 sprint 时间段内完成选定的 product backlog
item。三,团队身边有一个有经验的高级人员,负责发现问题或障碍,并想方设法解决。四,团队
成员间的中间产物可以呈现短时间的富余或短缺状态,但总体而言,是不多不少,比如开发人员可
能开发出某段程序,而测试人员还没有设计好相应的用例,或是测试人员更早的设计了用例,等待
开发人员实现所需的代码。

第五点,是我认为最重要的一点,我猜测有,但并没有很确切地看到麦当劳有这样的机制,那就是
流程的状态实时查询或显示,而且是非常可视化,非常直观的方式。比如说,麦当劳完全可以统计
出中午 11 点半到 12 点半之间,对于特级板烧鸡腿堡的需求量,可以有历史数据,也可以有当日动
态数据,那么后台操作台可以根据此数据,做好一定的准备工作,为即将到来的就餐高峰准备好足
量的汉堡,减少顾客等待的时间。而在 scrum 中,可以通过 Continuous Integration(持续集成)
等方法来达到此目的。如果在持续集成的系统当中,被执行的功能性测试用例都是基于用户需求的,
那么在 sprint 开发过程中,不断更新的测试用例执行状况,就是对开发状况的一个直观展示。在持
续集成中拥有单元测试案例的话,则可以获悉更小粒度的开发进度信息。抛开这些产品自身属性方
面的反馈信息,工作人员角度也可以获得足够的信息。在每天的 daily scrum,以及稍大项目中可
能使用的 scrum of scrums 中,开发团队都会透露出详细的开发状况介绍,甚至包括情感性的描述
“我们肯定能按期完成”。这些信息都能够给产品负责人或项目管理者(如果还有这个角色的话。。)
非常直观的印象,从而可以为开发工作的顺利进展和如期完成提供所需的支持,或增加更多可以完
成的任务。

但是,一个非常大的区别,至少我所处的项目环境和麦当劳之间的不同,是,麦当劳是零售业态,
接待员直接面对的是顾客,也即终端用户,客户需求是零碎的、变化的、快速的、产生速率是变化
的。他们递交的产品也是可重复的,每个产品个体的差异小到可忽略不计,且不存在后期维护成本,
无需进行产品设计,也无需考虑后期维护及扩展的需求。而软件开发则不同,在一个大型系统的开
发中,每个团队所面对的并非终端用户,一般是自己的上一级开发团队,比如系统平台开发团队的
直接客户是应用层开发团队,而且作为一个复杂的系统,其客户需求通常是有组织的、大量的、少
量变化的、产生速率恒定或受控制的。所递交的产品通常对整体重用性要求不高,必须考虑后期维
护及扩展所需要的成本,并需要进行产品设计,以满足对扩展及维护的需求,降低后期成本。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷工具

欲善敏捷开发 先利敏捷工具
Jack Vaughan
袁发明译

敏捷开发的潮流并不是由敏捷工具来推动的。因为你可以仅使用命令行接口、单元测
试工具和需求卡片来展开敏捷开发。但近年来,为了更好地支持敏捷开发,敏捷工具
也有了很大的发展。其中部分工具是直接面向新型项目管理方式的。

特别是有些种类的工具已与敏捷开发密不可分。根据 Forrester 研究公司(Forrester


Research)高级分析师 Carey Schwaber 的研究结果,面向敏捷开发的项目管理工具、持续集成构建
工具和自动测试工具已是敏捷开发不可或缺的工具。

她在《敏捷过程研究(The Truth About Agile Processes)》的报告中指出,“敏捷开发团队将投


资主要用于其团队所必需的工具,其中最先考虑的是敏捷项目管理工具,然后依次是测试工具、构
建管理工具和软件配置管理工具。”

Schwaber 还表示,各敏捷团队都有自己的管理方式,因此,他们对项目管理工具也有不同的需求。
尽管如此,仍然有些团队仅靠电子表格和 WIKI 进行管理。不过 Schwaber 等人也指出,当敏捷成为
大型团队开发进行大型项目的主流开发方式时,这些自己临时组织起来的技术将难以满足需求。

敏捷开发催生敏捷工具

敏捷开发在 20 世纪 90 年代晚期随极限编程(Extreme Programming,XP)运动而兴起。极限编程主


要是关于单元测试、结队编程和简化的需求采集的运用,当然也有人会说极限编程还包括大量的提
神饮料。

诸多的项目失败是促使极限编程出现的原因之一,比如无法准时交付产品,或者虽然成功交付却因
为产品不符合客户需求而不能投入使用。牵涉诸多预先分析和建模的统一建模语言(UML)和统一开
发过程(RUP)的发展也是极限编程形成的原因。

可以认为,2001 年的《敏捷宣言(Agile Manifesto)》是对敏捷过程最好的阐释。在某种程度上,


敏捷过程将极限编程系统化,为敏捷开发提供了参考标准和价值,并减轻开发人员面对需求变化时
的压力。

基本上,这是一个致力于寻找缩短交付间隔,并增加迭代频率方法的敏捷潮流。团队有权限自行设
定交付期限。交付可以使用的软件是最为重要的目标,而预先建模和需求采集阶段则要求尽量简单。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

36
37

欲善敏捷开发 先利敏捷工具

并且,需求采集并不是在项目早期便结束,而是会在项目开始后很长时间内一直进行的过程。

正因为以上原因,单元测试在敏捷开发中变得尤为重要。这样,在持续集成软件单元时仍然可以迅
速分析漏洞。而且,有效的自动构建工具也成为敏捷工具的组成部分。因为持续的迭代开发意味着
经常要进行快节奏的集成。授权给开发团队可以开阔项目开发思路,也使增强的项目管理软件在敏
捷开发中得以发挥更重要的作用。

Forrester 的 分 析 师 Schwaber 称 这 种 新 兴 的 项 目 工 具 为 “ 面 向 目 标 的 敏 捷 项 目 管 理 工 具
(purpose-built Agile project management tools)”。她重点提到在这一领域有代表性的敏捷
工具开发商:Rally 软件开发公司,VersionOne 和 ThoughtWorks 工作室。她还指出,目前已有可用
的敏捷模板,比如 Conchango 公司的 Scrum 模板便是 Microsoft Visual Studio Team System 开发
平台的杰出插件。

最新敏捷工具一览

2007 年,敏捷项目管理工具开发商都忙于新产品的迭代开发以满足其敏捷客户的需求。这些产品支
持新的工具插件,增强需求处理功能,并且:
* Rally 2007.7 版本支持用户需求的筛选、扩展的筛选标准、改进版本剩余时间表、新的通知
规则(notification rules),以及用于 Eclipse 和 CruiseControl.NET 的连接器。
* TargetProcess 2.6 添加了列表即时编辑功能(inline editing in lists)、新迭代规划
(iteration planning)功能和 Visual Source Safe 集成。
* VersionOne V1 最新版本中增加了第三方开源集成工具 Subversion 和 FitNesse。

著名的 IT 咨询公司 Thoughtworks 也开始进入这一领域。2007 年 10 月,公司发布用于敏捷软件开


发的 Mingle 1.1。虽然在第一个版本发布仅三个月就发布新版本,但公司声称这个版本可以更好地
帮助项目团队进行规划、追踪、优先分级和协作。Mingle 1.1 中的日期属性有助于对需求、漏洞和
任务进行追踪和划分优先级。并且,这个版本扩展了需求卡片(story card)的筛选范围,还增加
了对远程 Subversion 知识共享库的支持。

IBM 的 Rational 工具组有时会受到敏捷拥护者的指责,


特别是对其 Rational 统一开发过程
(Rational
Unified Process)的指责。因为人们普遍认为它是一个自上而下的过程,这会增加一线开发人员的
工作负担。2006 年,为传播敏捷方法,敏捷传道者 Scott Ambler 加入 IBM 公司。然后,2007 年,
IBM 发布其努力的成果——Jazz 开发平台。

设计模式(Design Patterns)领域最有影响力的拥护者,目前也在 IBM 进行 Jazz 开发的工程师 Erich


Gamma 说,Jazz 延续了 IBM Eclipse 的成功。最终,这个新软件将提供对定制过程的支持。他说,
“在开发 Jazz 的过程中,我们发现各团队都采用各自的过程,比如不同的开发方式,来处理规则变
化。”

目前,Jazz 仍然是一个(功能有限的)测试中的技术,但 IBM 公司已经展示了以 Jazz 技术为基础

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


敏捷工具

开发的主要用来提高团队开发效率的 IBM Rational Team Concert 协作平台。

敏捷开发加速产品交付

敏捷开发过程是经常变化的。同样,敏捷工具也是可以改变的。DTS 公司(Data Transfer Solutions)


的高级项目经理 Chris Spagnuolo 和 GIS 软件首席架构师 Dave Bouwman 在团队正式引进敏捷方法之
前已经使用了一年半的敏捷方法。他们的敏捷开发正在迅速展开。

引入敏捷方法之前,所有的开发对 Spagnuolo 和 Bouwman 来说都意味着冗长的需求列表和大量的预


先设计。但随着敏捷方法的到来,这些都不再是让人头痛的问题。他们选择的是基于 Scrum 技术的
敏捷方案,可以流水线化初始阶段的需求采集,可以在整个项目周期中随时增加需求,并可以集中
开发按周交付使用的软件版本。

刚开始,他们使用一些临时拼凑起来的、简单的项目管理工具。而现在他们接受了 Rally 软件开发


公司的项目管理工具。

Bouwman 和 Spagnuolo 在公司主要负责空间管理及相关资产管理应用软件的开发。Spanguoloyu 说,


“以前,我们要预先做大量的需求分析。采用敏捷方法以后,我们更换了工作方式。现在我们在整
个项目周期做需求采集,而不仅仅是项目开始时。这样,客户每周都可以对需求进行优先分级。”

Bouwman 说,“敏捷开发可以让我们先交付最有用的部分。我们很快便发现了这么做的好处。因为
每次你给他们展示一些东西以后,他们的需求都会发生变化,然后我们就能得到一些反馈。这种反
馈是双向的。你可以知道增量版本功能是否符合要求,而客户则可以知道你现在在干什么。”

对 Spagnuolo 来说,敏捷开发还意味着客户也变得“敏捷”。他说,“他们可以经常看到我们交付
的软件,需求改变也很灵活。”

Bouwman 补充说,“软件是一种比较飘渺的东西,但通过频繁的迭代开发和‘利益相关人’的持续
反馈,客户可以获得比较具体的感觉。他们还会告诉你下一步应该怎么做。”

然后他又说道,“虽然也预先做需求分析,但通常并不一定按你想的发展。实时的需求采集要有用
得多。”

敏捷工具改善敏捷开发

Spanguolo 还提到一个常见问题:索引卡片和即时贴反映的需求有时并不准确。随着项目复杂性提
高,仅使用简单的工具开始有点力不从心。他说,“我们开始寻找工具,特别是需求方面的工具”。
他们曾经使用一张电子表格追踪用户需求,然后根据需求用另一张电子表格安排各迭代周期的任务。

他说,“开始的时候这个方法还是有效的。但随着敏捷开发复杂度提高,以及参与的开发人员的增

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

38
39

欲善敏捷开发 先利敏捷工具

加,电子表格已经无法满足我们的需求。单是管理这些表格就要花费太多时间——每周需要大约四
到六个小时。”

Spanguolo 的团队也曾使用任务卡来组织工作。和需要大量工作的电子表格一样,低技术含量的任
务卡同样费时费力。Bouwman 说,“整理任务卡是一个艰苦的手工过程。”

后来 Spanguolo 他们开始使用 Rally 项目管理软件,团队才真正做到“实时追踪”,按照计划交付


成果,并能减轻开发人员的压力。 Spanguolo 还说,简报页面(reporting dashboard)可以让开
发人员更有效地组织任务。其中成功完成的版本以绿色显示,失败的版本以红色显示。

Rally 软件是一个核心工具,但是功能很多。Spagnuolo 和 Bouwman 的团队工作于.NET 环境下。他


们使用 MS Build 构建工具和 CruiseControl.NET 持续集成工具。CruiseControl.NET 对源码控制工
具进行监控,比如上文提到的 Subversion。如果有新的变动,它就会启动一个新的构建过程。然后
Rally 各组件会与这个过程或持续集成引擎相连。

展望敏捷工具前景

目前看来,在良好的知识共享库和智能项目数据管理工具的支持下,围绕构建管理和工作流程的标
准过程正在形成中。尽管如此,这些被 Burton Group 分析师 Joe Niski 称为“系统开发周期(SDLC)
基础结构”的每一步发展都将提高项目开发和项目团队的效率。

Niski 认为,关于知识共享库(repository)最关键的一点是“它存储了特定项目的元数据,其中
源代码控制库正是我们构建项目所需要的”。作为储存项目管理软件所依赖的知识共享库也存储了
构建成果。

当然,作为敏捷过程核心的是用户需求。这些用户需求(user stories)正在取代用例(use cases)


成为应用广泛的需求采集方法。并且,在诸多敏捷开发过程中,多页的用户需求已经逐渐被简单的
需求记录卡所取代。

新兴的敏捷项目管理工具大都支持以位图或 jpeg 格式存储这种记录卡。可以预计,这种“敏捷存储”


方式也将获得广泛应用。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008


好书推荐

好书推荐

敏捷软件开发:原则、模式与实践

Agile Software Development : Principles,

Patterns, and Practices

评价:

作者:Robert C. Martin

译者:邓辉

出版日期:2003-9-1

出版社:清华大学出版社

ISBN:7302071977

市场价:¥59.00 元

本书于 2003 年 6 月荣获美国《软件开发》第 13 届震憾(Jolt)大奖!

享誉全球的软件开发专家和软件工程大师 Robert C.Martin 向您介绍如何解决软件开发人员、项目


经理及软件项目领导们所面临的最棘手的问题。这本综合性、实用性的敏捷开发和极限编程方面的
指南,讲述了在预算和时间要求下软件开发人员和项目经理如何使用敏捷开发完成项目:使用真实
案例讲解如何用极限编程来设计、测试、重构和结对编程;包含了极具价值的可重用的 C++和 Java
源代码;还重点讲述了如何使用 UML 和设计模式解决面向客户系统的问题。本书于 2003 年荣获第
13 届软件开发图书震撼大奖,适于用作高校计算机专业本科生、研究生和软件学院的软件工程和软
件开发相关课程的教材或参考书,也适于软件开发和管理人员提高自身水平学习之用。

特色内容
Ø 讲述在预算和时间要求下,软件开发人员和项目经理如何使用敏捷开发完成项目。
Ø 使用真实案例讲解如何用极限编程来设计、测试、量构和结对编程。
Ø 包含了极具价值的可多次使用的 C++和 JAVA 源代码。
Ø 重点讲述了如何使用 UML 和设计模式解决面向客户系统。

捷道·敏捷堂 | http://www.agiledon.com ©Copyright 2008

40

You might also like