作者:Amr Elssamadisy 著,simaetin 译
本文选自:UMLCHINA
2002年05月13日
我们在ThoughtWorks这样的大型项目中应用XP方法的时间超过了15个月。这个项目开始于三年前,那时它有大量的需求文档和几个独立的功能小组。从2000年1月起,我们决定应用XP,虽然当时我们已经知道XP并不适用于大型项目。那时,我们需要向客户提交功能演示,并要通过提交一个工作子集而不仅仅是原型来赢得他们的信心(我们在团队中有超过25名开发人员和大约15名分析员)。但我们并未准备好功能,各个组都使用自己的代码框架,一个完整的应用无从谈起。简短地说,就是客户想要看到点什么,但我们还没有真正的功能。应该说开发的第一迭代期是成功的:我们给客户提交了一个真正的应用子集。成功的首要原因是我们让来自Asset, AR 和GUI组从事不同工作的开发者融合在一起。当然,其间也有"成长的烦恼":在分析员和开发者之间发生某些个人冲突,但我们成功跨越了这些障碍。那时一切都很美好:我们以飞快的速度成功地提交了功能演示,但随着时间的流逝和项目的继续,有些事情却不如我们所愿。这就是这篇文章要讲述的内容:我们真正的收获在"蜜月"期后,关于我们如何在一年半中管理一个50人的项目开发并及时完成阶段性的提交(尽管有时并不成功),关于我们经历的痛苦,我们后续的工作,我们最终学到的以及作为改进要用到下一个项目中的经验。
首先,我们给出我们开始的状况:15个月前我们是如何应用XP的,然后展示我们现在的XP应用情况。也就是说,下面将讨论我们改变技术的原因。这是一个自然选择的过程,它剔除了许多不好的想法,或把它们改造成更有效的方法,使之更适于大型项目。最后我们将总结并给出在大型项目中使用XP的谨慎建议。
大型项目中XP的元素
好吧,让我们来进入正题。我们开发和分析的队伍由大约35名开发人员、15名分析人员和10名QA组成。开发人员依赖分析人员作为项目的客户。尽管有实际的客户,分析人员还是要协同工作以便有选择地做出客户决策。下表演示了[1]所讨论的XP基本元素并简要介绍了这些方面如何应用到项目的各个阶段。我们用这个表来分析我们团队自然选择的实践,以及书本上的XP如何应用到一个超过50人的大型项目中。
|
计划 |
提交周期 |
比喻 |
设计的简单性 |
测试 |
重构 |
1/2000 |
大跨度的迭代计划会议。开发和分析组的全部成员整天在一起讨论新的故事卡片和预估。大多数开发者签入(sign up)新的功能。 |
1个月 |
无 |
转变已有的代码基准。已有的代码依然复杂,但新的代码要尽可能简单。这个阶段包括抛弃老的代码和重写那些"今后可能会被用到"的功能代码。 |
单元测试开始于一些新的代码。推动建立一个大的测试基准。QA做所有的功能测试并有权留弃故事卡片。 |
对于旧有代码,如有必要就进行重构。 |
7/2000 |
基本同1/2000,但确实感觉十分低效。多数与会者没能很好地参与。拖沓的讨论,50人的会议是无法忍受的。 |
1个月 |
无 |
以尽可能简单的原则继续设计。完成对已有设计的重构。完成代码会审,旨在全体范围中讨论新设计以便整个团队了解代码和设计的发展趋势。 |
更好的单元测试覆盖更大的范围,尽管没有完全覆盖。代码功能测试帮助覆盖测试范围。 |
多数开发人员埋头于新功能的开发,很少会去做重构。代码基准在迭代期末向QA组提交卡片时变得糟糕起来。 |
1/2001 |
希望能尽快做出每个迭代期间的计划-我们的办法是在正式会议前以小组为单位进行更多的准备工作。 |
2 周 |
无 |
多数的设计基于已有的设计:保留标准,代码会审逐渐停止,因为新的设计和重构尚未完成。 |
试图去掉代码功能测试而代之以屏幕搜刮(Screen Scraper),若失败就回到代码功能测试 。加入新的单元测试但仍然没有全部覆盖。QA组开始结合界面测试使用自动功能测试。 |
重构开始更频繁,因为部分代码开始变得凌乱不堪。需要清理的原因主要是实现简单和迭代期限使得代码在没有重要重构的情况下增长。 |
6/2001 |
举行一些讨论卡片或相关卡片组的会议,参加者包括对这些功能感兴趣或有经验的开发者和负责这些卡片的分析人员。 |
2 周 |
无 |
队伍中的大多数人及整个QA组准备提交1.0版本给客户。代码的基准分离以便加入没有测试的新功能。对单元测试有更多的依赖。 |
尽管仍有遗漏,测试范围已经基本稳定。QA组不再测试新功能,因为焦点是1.0版本的提交。 |
在发布版上做的重构很少,而在继续开发的版本上,开发人员会很尽责地进行重构,特别是在年初被迫做了更大更痛苦的重构以后. |
|
结对编程 |
集体所有 |
持续集成 |
40小时周 |
在场客户 |
代码规范 |
1/2000 |
由于我们决定采用XP,整个团队读了[1]。每个人都做了尝试,多数人被吸引。 |
由于最初阶段的分组是面向功能,因此此时我们并未意识到集体所有 (collective ownership) ,也没有意识到代码的保护。 |
从第一迭代期开始,进行在线集成,参见[2] |
这是一个概念工作时间:因为我们希望客户在场。于是我们会花另外的时间来满足最后期限。 |
事务分析人员是在场的客户,他们15人一组。真正的客户是不在的,由分析人员同他们沟通。 基本上是JAVA的一般语法。 |
7/2000 |
结对编程依旧盛行:开发者对新功能进行结对编程,但改错和维护工作由单个人来做。也有一些开发者停止了结对编程。 |
当开发者越来越多地接触系统不同的部分的时候,代码的所有者逐渐显现出来。成员间通过闲谈,代码互审和短小的站立会议(Stand up meeting)进行很好的沟通。 |
代码功能测试加入构造过程。 |
为了通过故事卡片,在每个迭代期的最后工作时间达到50至60小时。 |
同上 |
两周一次的代码互审给开发人员一个机会讨论不同子系统的实现方式。我们可以接受某些子系统代码的非正式方式。 |
1/2001 |
结对编程更少了,因为这一阶段编码更直接,而有些人在进行重构。 |
因为效率低,站立会议被弃用,但代码的所有显现得更加清晰。开发人员开始专职负责系统的某部分。 |
稳定_同上。 |
以2周为迭代期,开发人员的工作时间更接近于40小时… |
同上 |
代码互审逐渐减少,设计及编码规范趋于饱和。 |
6/2001 |
定下了规则:所有新功能要应用结对编程,而改错和维护则由一人完成。 |
随着专业化分工的继续,不同的开发小组人员拥有不同部分代码的知识,于是我们将使他们在接下来相应模块的设计中起更活跃的作用,但代码仍是集体共有。 |
稳定同上。 |
同上 |
同上 |
同上 |
结对编程 首先我们说说结对编程的体会。多数情况下,我们在某个迭代期间有两个开发人员同时为一个故事卡片(或几个迭代期的相关卡片)工作。在大型项目中开发人员需要投入更多的关注,因为开始新领域的编码的热身时间是不容忽视的。开发人员间良好的沟通和周期性的计划会议让每个人都具有谁在做什么的整体概念。这使得经典教科书中开发者甲找到开发者乙要求共同解决问题的结对编程方式成为可能。
结对编程当然很好,但并非任何时侯都适用。最通常的情况是,在改错和维护时开发人员并不愿结对,并且这种情况下许多眼睛盯着调试代码也确实没什么好处。再有就是迭代期间的重复工作,这种情况下,问题的解决方案已经确定,不必再结对了。
还有,开发人员有不同的个性:有些人需要间歇性的结对编程,而有些人更加出色,结对编程会妨碍他们才能的发挥而最终成为他们的负担。
单元测试和集成构造 单元测试和集成构造绝对是必要的,这意味着如果我们没有测试,我们就不能提交任何代码。当应用
程序变得越来越大时,没有测试我们就不能加入任何新功能或进行重构。我们现在在新代码进库时会有集成构造和测试。关于这些构造及测试的细节,负责人员会及时放在内部网页上,这样每个开发人员都能知道当前的构造状态,事务分析员和QA能拿到最近构造的信息来测试新的功能。
组内所有和信息共享
对于这样一个大型的项目,为了防止被分为孤立的部分而使整个系统做出不适当的假设,信息的发布和不同部分的代码轮作非常重要。沟通是必需的(但我们无法强迫一个沉默的人开口)于是我们试图在每两周一次的短会上给每个人发言的机会,以使沉默寡言的人能说出他们的要求。而最终我们取消了这样的会议,因为大多数开发人员认为这种把每件事都蜻蜓点水地提一下的会议只是浪费时间。这也是这个团队的优点之一??我们总能象一个共同体般地工作,如同结对编程一样地合作。
开始,我们采用了轮作的方法,也就是每个人对每件事都作一点,这使得我们在后来快到截止期限的时候都在从事己经了解的事情。但对于一段复杂的代码,要做到这一点,时间投入非常巨大。最好采用折衷的办法:也
就是在项目时间紧的时候只作每个人熟悉的工作,而在其它时候,比如改错、研究或正在做一项熟悉的工作时,可以同时做一件不熟悉的事。我们现在的原则是,开发人员在几个迭代周期中连续做一些相关的卡片,同时逐渐地转向系统的其它部分。在同一个迭代期内签入几个不同卡片的做法己经不再用了。
代码确实会越变越糟。是因为我们的项目有些大吗?还是因为许多做代码的人都是新手(是指这个功能领域中的新手,而非编程新手)?答案可能是二者皆有吧。但有时我们不接触代码,很难开始系统的其它部分,因此定期清理代码是必须的。这样引出我们下一个论题:重构。
重构
在应用XP的大型项目中,为了消除代码的不一致性,重构是绝对必要的。即便对于那些对项目的应用领域很熟悉的人,也会面对重构的巨大工作量望而却步。对于项目经理来说,必须认识到重构需要另行分配时间。我们做到了这一点,我们留出了时间来重构代码的主要部分。
短迭代周期
迭代期及其期限是必须的,但长度一直是个问题。过去我们采用长迭代周期(如一个月),而每到月末就会很紧张并伴随着一些不良代码的加入,并且不可避免地在估计上出现问题。我们不得不接受一些未做的卡片(这对开发人员来说非常困难,并且难以满足既定的期限)。
总结
下面把18个月中我们在这个50人项目的经验和教训列出如下:
1) 在每个迭代期开始时进行迭代计划会议。每日客户和开发人员讨论最近的故事卡片并评估它们,在每天的讨论结束后重新分组并演示这些评价和发现,然后让开发人员签入。这可以让整个项目组知道项目的当前情况而不用让每个人都卷入马拉松式的会议。
2) 使提交版本周期尽可能短:我们是两周一次,但在必要的时候也可以使提交跨多个迭代周期。允许在多个迭代周期签入卡片,但要以每个周期为单位进行进度的监控。
3) 进行尽可能多的单元测试,这是不言而喻的。应当有一个自动进行功能测试的软件包以保证测试的覆盖范围。但是QA组是不可替代的(无论开发人员写过多少测试程序),因为我们对系统怎样工作总是存在偏见。
4) 简单的设计能帮助我们连续向客户提供可工作的版本。频繁的设计会议对于加入大量新功能时是很有用的,而午餐是一个召集整个项目组的好时候。这可以避免在系统的不同部分同时存在不兼容的解决方案。
5) 重构是能够做到简单设计的唯一方法。设计的重构与代码的重构同等重要。尽管不进行重构而采用打补丁的解决方法往往是有吸引力的,但是如果一个系统的补丁太多,那就意味着今后它将进行更大的重构。
6) 在加入新功能时,要坚定不移地贯彻结对编程,而在改错和做重复性工作时停止,因为问题已经在结对时解决了。
7) 集体所有和沟通密不可分。团队必须有一种有效的沟通方式。也许不仅仅是非正式的讨论,有时定期的10到15分钟的发布会是很好的方式。
8) 在大型项目中,一些人要扮演客户的角色,为大量开发人员产生足够的工作。这和领域知识密切相关。
9) 编码标准是十分非正式的,这不会损害你的进度。更重要的是一种通过演示的沟通。代码不是文档的全部,开发人员需要看到全貌,这是代码不能提供的。
也有一些规则我们没有实行:
1) 两周一次的站立会议是低效的。可以选择每月一次的迭代团队通气会来替代。
2) 迭代计划会议没有必要让全部人员参与。更好的方式是按更小的组进行研讨,而在每天下班前用30到45分钟对卡片进行讨论。
3) 一个月的迭代期太长,不利于产生高质量的代码。2周一次的迭代周期更容易跟踪并使估计更准确。
4) 上一点在大代码量时并不合适,特别是重构大规模系统时。这时一张卡片会影响到多个迭代期。
5) 比喻(Metaphor)对于大型系统是不适合的。
6) 每周40小时我们来说不成问题,40小时是最少的情况,超时工作并没有给我们带来不利的影响。要重申的是,我们不是100%的实行结对编程。
这就是XP,或者说我们的XP版本,在我们小组所做的工作。我们经历了按时提交大型复杂应用的过程,也为每个开发人员提供了应对此类项目的宝贵经验。
参考书目
1. Beck, K. Extreme Programming Explained: Embrace Change. Addison-Wesley, 1999; ISBN201-61641-6
2. Fowler, M. and Foemmel, M.; Continuous Integration