UML软件工程组织

揭开极端编程的神秘面纱:“XP 精华”重访,第 2 部分
Roy W. Millerrmiller@rolemodelsoft.com
软件开发人员,RoleModel Software, Inc.
程序员方法
测试优先的开发
结对编程
重构
集体所有权
持续的集成
YAGNI
方法协作
下个月
揭开极端编程的神秘面纱系列的这个月的文章中,Roy Miller 解释了作为 XP 团队的一个程序员意味着什么,以及六种程序员方法如何适合特定的环境。虽然 19 种 XP 方法都很重要,但对于一个开发软件的团队来说,程序员方法是最重要的

程序员方法:创建系统
XP 程序员方法描述了程序员在创建团队所需的系统时需要做什么事情。对于许多人来说,这些方法就是 XP。当然,XP 不仅仅是关于编写代码的,但如果没有代码,整个过程就是在浪费时间。就象上个月一样,这些方法中的有一些并不在最初的 12 种 XP 方法之列。在每个名称后,我都会插入说明,指出那种方法是新的、未改动的,还是映射到初始名称的。注意,这些名称是不断变化的,但原则应该不会变。(您可能已经注意到,最初的一个程序员方法,即编码标准(Coding Standard),已经不在列表中了。原因是这个方法在新的列表中将是多余的。编码标准将在程序员编写代码时出现。您没必要将它作为一种单独的方法。)

在我们讨论程序员方法之前,我们先来弄清楚上个月的一些问题。我将在这个专栏中讨论的修改过的/重命名的/新的方法是我经过对 Kent Beck 写的两篇尚未发表的文章进行深思熟虑后的结果。据我所知,XP 目前还没有经过任何官方更改,这些方法和思想还没有以任何正式的方式出现在领导组或类似的位置上。可能不会有任何官方的更改。当然也没有任何种类的对这些方法进行正式修订的工作。我想此刻我最想说的是我所写的可能会成为将来某个时候的 XP。可能看起来会有点不同。我希望我的想法会对这些讨论有所帮助。阅读时风险自负。

测试优先的开发(映射到测试(testing))
无论程序员什么时候修改了代码,他们都需要知道自己刚才所做的是改善了代码还是破坏了一些东西。更重要的是,他们要维持必要的纪律,从而只需创建最少数量的代码就能够完成所需的工作,而不是创建规模庞大的代码,里面包含一大堆可能后来有人会需要的东西。这就是测试优先的开发的主旨所在。

在 XP 中有两种测试:单元测试和验收测试。这些是典型的名称,但我不喜欢。对于我来说,这两个名字太莫名其妙了。我更喜欢 Ron Jeffries 在他写的“What is Extreme Programming?”(请参阅参考资料)中建议的名字:客户测试和程序员测试。

这两个名称一语中的地道出了施行这两类测试的原因。程序员在编写代码的同时编写程序员测试。客户在定义了素材(stories)后编写客户测试。程序员测试告诉开发人员系统是不是在任何时刻都能正常运行(象客户定义的那样)。客户测试告诉团队系统是否执行用户希望它做的工作。在这里我将讨论程序员测试,并将在下个月讨论客户测试。

假设团队使用的是一种面向对象的语言,如 Java 语言,开发者在为每个有可能崩溃的方法编码之前为其编写程序员测试(大多数时候,只是为公共接口编写程序员测试)。然后他们只需编写足够的代码使其能通过测试。有时人们会发现这有点奇怪,但答案很简单。编写测试首先可以为您提供:

  • 一组可能最完整的测试,它可以增加您对代码的信心
  • 可能能工作的最简单的代码,这使得后来重构代码更为简单。
  • 更清楚的代码意图,使得后来的理解和重构更加容易。

开发人员只有在通过所有程序员测试后才可以将代码检入到源代码资源库中。程序员测试使开发人员有信心相信他们的代码能够工作。他们留下了线索,使其他开发者能够理解最初的开发者的意图(我很少看到过比这更好的代码文档)。程序员测试还给予开发者重构代码的勇气,因为测试失败会立即告诉开发者是不是有地方出了问题。程序员测试应该实现自动化,并且要给出清楚的通过或失败结果。xUnit 框架(请参阅参考资料)做到的远不止这些,因此,我所知道的大多数 XP 团队都使用它们。几乎您能想到的每种语言都有自己的 xUnit。只需用您选择的语言或工具代替“x”即可(例如,Microsoft Visual Basic 的 VBUnit、C++ 的 CppUnit 等等)。

作为一名程序员,我常常对使用程序员测试进行编码与不使用程序员测试进行编码之间的差别感到吃惊。我发现自己在编写代码时会执行一些非常小的步骤。实际上,在我执行一些小到一定程度的步骤时,我不必进行太多的调试工作,因为问题的根源会变得很明显:它就出在我正在编写的那行代码上。对我来说,比较明显的就是首先编写测试经常会推动我尽可能去创建最简单的代码。

我确信您已经编写了您认为以后会需要的代码,但您真的需要它们吗?可能会需要,但如果不需要的话,您会除去它们吗?或许不会。所以,它们只是放在那里,而没什么用处。如果您的测试确切地告诉您要写多少代码会怎样?如果您只编写足够通过测试的代码,而不写多余的代码会怎样?可能您拥有的代码会更少,并且它们都会被派上用场。这就是我想编写的那种代码。当您编写自己的测试代码时,测试会推动(drive)代码。结果,当测试推动代码时,您的代码就会大有改观。根据我的经验,在测试推动代码时,我的代码就会简单得多,而且我一直在设法使它们更为简单。Kent Beck 正在写一本名为 Test-Driven Development 的书(请参阅参考资料),这本书详细讲解了这种方法。我向您推荐它。实际上,我更倾向于把这种方法命名为测试推动的开发(Test-Driven Development)

结对编程(未改动)
在 XP 中,所有的生产代码都是由结对的开发者编写的。大多数开发者都从不曾有过与另一个人一起实际编写代码的经历,刚开始会感到有点不习惯。结对编程通常意味着两名开发者共享一台计算机。一个人键入代码(“司机”(driver)),另一个人帮他指路(“领航员”(navigator)或称“伙伴”(partner))。如果司机被困住了(或失败了),或者如果领航员有一个好主意但不通过键盘输入就不能很好地描述,那么当前的司机可以放弃控制权,做一会儿领航员。每个人都应该经常切换角色。一旦您习惯了这些角色,来回切换就会相当轻松。

这种方法听起来好象效率不高。我一直很喜欢 Martin Fowler 的回答:“当人们说结对编程会降低工作效率时,我回答说,'如果编程最耗时间的部分是打字,那么这是对的。'”实际上,结对编程或者简称“结对”,可以提供许多好处,包括:

  • 所有的设计决定都用了至少两个人的智慧。
  • 至少有两个人熟悉系统的每一部分。
  • 两个人都忽略测试或其它任务的可能性更小。
  • 改变各对的组合可以在团队范围内传播知识
  • 代码通常至少被一个人复查。

实验法研究指出代码复查可以提高代码的质量,但是我讨厌那样做。我还认为要正确地进行复查将非常困难,复查工作没有结对编程那么有效。如果每行生产代码都被一个对代码的意图非常熟悉的人复查过会怎样?结对编程为您提供的刚好就是这一点。只有真正参与才能足够忠实。

Alistair Cockburn 与 Laurie Williams 合写的 The Costs and Benefits of Pair Programming(请参阅参考资料)中讨论的研究也表明结对编程实际上要比单独编程效率高。这与我们的直觉有点不一样。大多数管理人员(我曾经是其中之一)将看到两个开发者做一个开发者的工作,然后就看不出更多的东西了。这不是理性的思维方式。这种想法过于简单。这也是错误的。

至于风险,请考虑一会儿项目为什么会失败。其中一个主要的原因就是太依赖个人英雄。如果承担项目的英雄意外阵亡,那么您的项目可能就完了。结对的本质是要散播知识。结对的两个人应该经常切换角色。如果结对的两个人的角色都很固定,他们就会被困住。

尽管有不少人推崇结对,也有很多强有力的观点来证明结对,但大多数开发者仍讨厌这种思想。或许这是自尊心的问题。我相信,大多数开发者在内心深处都想当英雄。结对使得这一点几乎不可能实现。就象我在 Extreme Programming Applied: Playing to Win(请参阅参考资料)中所写的那样:

每个人都可以坐在其他人的旁边,并时常不经邀请就发表一些意见。许多人可以完全投入并尽力使结果更好。但真正理解结对的人知道那意味着要去喜欢另一个人。

就象我在那本书中所说的那样,我讨论的那种爱是通过行动表现出来的,不是浪漫的那种。如果您爱另一个人,您就会尽力去发现他的优点,并帮助他成长。您将是耐心的、亲切的、不嫉妒、大方的、谦逊的且有礼貌的。这种热情的人与人之间的关系,即使是只几个小时,大多数人也不感兴趣,而开发者毕竟也是人。这是一种激进的思想,并不适合每个人。但它也可以为您的职业生涯带来最有益的经历。

重构(未改动)
重构是一种改进代码但不改变功能的技术。XP 团队在重构时毫不手软。开发者进行重构的两个关键时机是:在实现一个功能之前和之后。开发者设法确定更改现有的代码会不会使实现新功能更容易。他们查看自己刚写好的代码,看看有没有办法简化它们。例如,如果他们看到有抽象的机会,他们就会进行重构,从具体的实现中除去重复的代码。这里要注意的重要一点是,您应该要么设计并编写新代码,要么重构现有的代码。不要试图一次进行这两种工作。

虽然 XP 建议您应该编写可能运行的最简单的代码,但它还建议您应该不断学习。重构使您能把自己学到的东西结合到代码中去,同时又不会破坏测试。它使您的代码保持简练。这意味着代码存在的时间会更长,为后来的开发者引入的问题更少,并为他们指引正确的方向。

重构的目的是改善现有代码的设计。团队应该拥有程序员测试和客户测试的自动化套件。前者应该在任何时候都通过,而后者应该达到客户所要求的程度。运行测试。重构代码。重新运行测试。有程序员测试失败了?修正它们。有“必需的”客户测试失败了?修正它们。如果您无法修正它们,请逆序恢复您刚刚试过的重构。如果不进行测试,修改代码就会象猜谜一样。重构时,如果代码崩溃了,测试将告诉您。

集体所有权(映射到集体代码所有权(collective code ownership))
团队中的任何人都应该有权修改任何代码来改善它。每个人都拥有全部代码,这意味着每个人都对它负责。这种技术可以让人们对代码片段进行必要的更改而不用经过各个代码拥有者个人的瓶颈。每个人都负责这一事实消除了无代码所有权所带来的混乱。

每个人都拥有代码并不是说无人拥有代码。没人拥有代码时,人们可以随处进行破坏而不必负任何责任。而 XP 说,“如果是您破坏的,应该您来弥补。”团队应该有一些必须在每次集成之前和之后运行的程序员测试。如果您破坏了某些内容,您要负责进行修补,无论它位于代码的哪一部分。这需要极度严格的纪律。

我已经注意到有些团队成员根本不执行这个方法。一想到有人会弄乱“他们的”代码,他们就受不了。如果想成为一个 XP 团队的一部分,您就必须共享所有代码的所有权。如果您不那样做,这个小组最终就会碰壁。代码的某些部分将脱离限制,这使得系统很难进行更改 — 这种情形是我们要设法避免的。如果某个人这样做了,团队需要指出这一点并鼓励他改变。如果他拒绝改变,就强烈建议他改变或离开。如果他仍然拒绝,那就告诉他走人。不要在这一点上妥协。

持续的集成(未改动)
每天多次向系统集成新的更改,并自动重新构建整个系统。运行了所有的测试后才能发布更改。如果一次测试失败了,您有两个选择,修正它并集成,或者不集成。只要有一次测试失败无法修正就意味着您不应该进行集成。

持续的集成并不意味着每秒都要集成,但它的确意味着您应该尽早并且经常集成。每天集成是不够的。在每天的八小时时间内,我开始有了这样一种讨厌的感觉,我是不是还没有至少每隔几个小时集成一次,是不是没有集成更多次。这听起来会引起许多人的恐慌。再一次,测试应该赶走这种恐惧。测试告诉我集成是否“工作”,是否可以安全地发布给小组的其他人。

YAGNI,或称“您将不需要它”(映射到简单的设计(simple design))
在“XP 精华”中,我和 Chris Collins 写道:“XP 的诽谤者声称这个过程忽略了设计。”他们仍然在这样批评,他们依然是错的,但我认为 XP 迷们有时太急着针对这种异议进行辩解了。

这种异议真正要说的是人们对紧急设计(emergent design)这种思想感到不舒服:允许系统设计不断出现变化,而不是开始时就设法把一切确定下来。确定一个方向,标出一些重要之处和转折点,然后开始进行。您可以在学习的过程中做一些调整。不喜欢这种方法的人会把它曲解为不守纪律。实际上,这是在目前的经济情况下最现实的开发方法。

典型的重量级方法建议您做的不过是提前完成大部分琐碎的设计任务。这就好象是拍一张静态的地平线的照片,静止不动,然后尝试画一张如何到达那里的完美的地图。如果需求是固定的,那么这算是一种好方法。如果您在刚开始时就知道系统需要做什么以及它需要如何做,您就可以预先进行大部分(如果不是全部)的设计工作。但实际上,大多数开发者都在探索他们以前没有充分解决的问题,或者正在实现以前还没有尝试过的解决方案。目前,大部分系统都是为在不断变化(而不是每十年才变化一次)的市场上进行竞争的商业设计的。

在这样的环境下,需求随时都会发生有影响的更改,需求的稳定只是个白日梦。这意味着大型的、提前的设计是不合适的。刚开始时,您根本无法知道最终您将在哪里结束,甚至不知道在哪里结束。您最好是确定一个大致的方向,并在进行的过程中做一些小的、经常的调整以满足不断变动的目标。XP 要求每一步都尽可能简单,这样就可以按必需的频度改变方向了。我们一直设法使用在任一点都可能工作的最简单的设计,并根据不断出现的实际情况改变这种设计。

按照 Kent Beck 的说法,可能工作的最简单的设计是具有下列特征的设计:

  • 运行所有的测试
  • 不包含重复的代码
  • 清楚地表明程序员对所有代码的意图
  • 包含尽可能少的类和方法

要求简单的设计并非暗示所有的设计都将很小或者无足轻重。只是让它们必须尽可能简单,并且仍能工作。不要包含不准备使用的“额外”功能。我们称这样的事物为 YAGNI,表示“您将不需要它(you aren't going to need it)。”换句话说,在设计时不要考虑您可能需要的内容。在下一次反复中,您可能会发现您根本不需要它。相反,您只需编写测试,然后编写足够的代码使其能通过测试。通常您只需做这些设计即可。应用 YAGNI 原则并不表示让您永远不要提前考虑事情,但的确意味着您不要考虑得太远。

方法协作
所有的 XP 方法在一起工作并互相补充。对于程序员方法尤其是如此。然而,某些程序员方法(例如,测试推动的开发与结对程序)可以独立施行,而其它方法(重构、集体所有权、持续的集成与 YAGNI)“依赖”程度较大,需要其它方法就绪它们才能够工作。

就象我以前所说的那样,重构而不进行测试我会感到不安。同样,没有测试告诉我是否破坏了某些内容,我就会发现集体所有权非常荒唐,持续的集成也是不可能的。至于 YAGNI,我认为如果没有测试推动您实现那种简单性,它不可能使您的设计简单。如果能够根据将来可能发生的错误来构建,这种诱惑实在难以抵挡。有趣的是,我注意到这些依赖性方法中的大部分方法都依赖于已经做过测试。(或许方法是特别重要的。)

虽然大多数方法可以独立施行,大多数都不可以。如果您有了一些测试,您或许能够进一步零零碎碎地采用更多其它的测试,但您为什么要这样做呢?把方法放在一起使用,让它们互补可以使团队取得令人吃惊的成功,并且可以开发出更好的软件。如果您认为自己不会喜欢某种方法,您可以忍住“绿蛋与汉堡(Green Eggs and Ham)”的诱惑并设法坚持一段时间。您可能会感到惊讶。如果您尝试过了某种方法,但仍不喜欢它,您可以不用该方法去尝试别的方法,但我认为您的速度和结果会因此而受到负面影响。

我仍坚持原来在“XP 精华”中的建议:整体大于部分之和。您可以实现单个方法或者一个小型方法子集,比不使用任何方法得到更大收益。但您只有在实现所有方法的情况下才能获得最多的收益,因为它们的力量来自于它们之间的相互作用。刚开始时可以把书本上的内容作为基准来执行 XP。一旦理解了方法的交互,您就拥有了让它们适应您自己的环境所需的知识。请记住,“进行 XP”不是目的,只是实现目的的一种方法。目的是快速开发出上好的软件。如果您的过程有了一些变化,已称不上是在进行 XP,但结果仍能让您战胜自己的竞争对手,那么您已经成功了。

下个月
本月的专栏向您概述了 XP 的程序员方法。下个月,我将讨论面向客户与管理层的方法,这些人也是我们的一个团队的一部分。如果您是一名程序员,并且还没有把这些人看作自己团队的一部分,那么您的软件开发方法就是不对的。如果您是一名业务人员,负责为一个项目提供商业方向,或者是一名经理,负责使整个项目沿正轨进行,并且您没有把自己看作是一个程序员团队的一部分,那么您就是大多数项目失败的原因。您需要以一种截然不同的方式参与到团队中去。下一个月,我将告诉您怎样做。


 

 

版权所有:UML软件工程组织