设计已死?
Martin Fowler著 Ai92译
英文原文版权由Martin Fowler拥有
Original text is copyrighted by Martin Fowler
Martin Fowler
Chief Scientist, ThoughtWorks
声明:任何人都可以在任何地方随意转载本文,但是在转载时请保持本文完整性,请不要在转载的时候做任何改动或增删。
*****************************************************************
对很多粗略接触到极限编程(Extreme Programming)的人来说,XP似乎宣告了软件设计的死刑。不仅有很多的设计被嘲笑为“臃肿的预先设计(Big Up Front Design)”,就连很多设计技术,像UML、灵活的程序架构(framework),甚至连模式(pattern)都不再受到重视甚至被彻底的忽略了。事实上,XP包含了很多的设计理念,但是它与现有的软件过程有着不同的运作方式。XP通过许多实践赋予演进式设计(evolutionary design)崭新的风貌,让演进成为一种可行的设计方法(viable design strategy)。但是它也带来了新的挑战和技巧,因为设计者(designer)要去学习如何使设计精简,如何使用重构来保持设计的简洁,以及如何使用模式。
(这篇文章是我在XP2000研讨会发表的演说,它会发表在研讨会讲义中。)
经过规划的设计与演进式的设计(Planned and Evolutionary Design)
XP实践(The Enabling Practices of XP)
简单的价值(The value of Simplicity)
究竟什么是简单(What on Earth is Simplicity Anyway)
重构违反了YAGNI吗?(Does Refactoring Violate YAGNI?)
模式与XP(Patterns and XP)
发展架构(Growing an Architecture)
UML与XP(UML and XP)
关于隐喻(On Metaphor)
你想成为架构师吗?(Do you wanna be an Architect when you grow up?)
可逆性(Reversibility)
设计自律(The Will to Design)
很难重构的东西(Things that are difficult to refactor in)
产生设计了吗?(Is Design Happening?)
这样来看设计死了吗?(So is Design Dead?)
致谢(Acknowledgements)
修订记录(Revision History)
XP挑战了很多在软件开发中普遍存在的行为。其中最受争议的就是反对up-front design,而支持演进的方式。批评者说这是退回到了仅有"code and fix" ,通常被嘲笑为黑客式的开发方式。而极限编程的支持者也往往被看作排斥设计技术(就像UML)、原则和模式的激进分子。不用担心什么设计,只要你倾听(listen to)你的代码,好的设计会浮现出来的。
我发现自己陷入了这个争论的中央。我从事的很多工作都和图形设计语言(graphical design languages)以及模式(patterns)有关,其中图形设计语言包括UML以及早于UML的其它图形设计语言。实际上我曾经写过关于UML和模式方面的书。我拥抱XP就意味着要放弃我曾经主张的一切,将这些“反革命”的观点统统从脑子中清除掉?
嗯... 我不想让你的思绪悬在这个疑问里。简单的说答案是否定的。接下来的文章就让我来详细说明。
经过规划的设计与演进式的设计(Planned and Evolutionary Design)
我将在这篇文章中描述软件开发中的两种设计方式。或许最常见的是演进式设计。它的本质是系统设计随着系统实现逐步增长。设计是编码过程中的一部分,并且随着代码的发展,设计也要跟着调整。
在常见的使用中,演进式设计带来的是灾难。设计的结果其实是一堆特定的战术策略(ad-hoc tactical decisions)的集合,使得代码变得更难以修改(alter)。从很多方面来看,你可能会争辩说这根本没有设计,当然会导致差劲的设计。像Kent表述的那样,所谓设计从长远的观点来看就是要做到很容易地变动软件。当设计开始变坏时,你应该能够做出有效的更改。一段时间之后,设计变得越来越糟,你就体会到了软件的混乱。这样的情形不仅使软件变得本身难以变动,也容易产生难以追踪和彻底解决的bug。随着工程的进行,修改不断出现的bug 所花费的成本呈指数地增长,这就是"code and fix"的噩梦。
Planned Design的做法正好相反,并且吸收了其它工程学中的观念。如果你打算做一间狗屋,你只需要找些木料并在心中有一个大略的构思就可以了。但是如果你想要建一栋摩天大楼,同样的做法,恐怕盖不到一半大楼就垮了。那么你要先在一间像我太太在波士顿市区那样的办公室里完成工程图。她在设计图中确定所有的细节,一部分使用数学分析,但是大部分都是使用建筑规范。所谓的建筑规范就是根据成功的经验 (有些是基础数学) 制定出的如何设计建筑的法则。一旦设计完成,她所在的设计公司就将设计图交给另一个公司按图施工。
Planned Design 将同样的方式应用在软件开发中。设计师预先设计出大部分得细节。设计师们并不负责编写代码,他们只负责设计。所以设计师们可以利用像UML这样的设计技术从编码的细节中脱离出来,而在一个比较抽象的层面上工作。一旦设计完成了,他们就可以将它交给另一个团队(甚至是另一家公司)去建造(build)。因为设计师们在宏观的层面上考虑问题,所以他们能够避免因为策略方面不断的更改而导致软件的失序。程序员遵循着设计好的方向,写出好的系统。
自从Planned design方法从七十年代出现,已经有很多人用过它了。在很多方面它比”code and fix”演进式设计要来的好。但是它也有一些缺点。第一个缺点是当你在编写代码时,你不可能把所有必须处理的问题都想清楚。所以将无可避免的遇到一些让人对原先设计产生质疑的问题。可是如果设计师在完成工作之后就转移到了其它项目,怎么办?程序员开始迁就设计来写程序,于是软件开始趋于混乱。就算找到了设计师,花时间整理设计,变更设计图,然后修改程序代码。但是必须面临更短促的时间压力来修改问题,又是混乱的开端。
此外,Planned design通常还有文化方面的问题。设计师有着熟练的技巧和丰富的经验。然而,他们忙于从事设计而没有时间来编写代码。可是,软件开发使用的工具和素材(materials)发生着日新月异的变化。当你不再编写代码时,你不仅仅错失了技术变革带来的好处,同时也失去了那些编写代码的程序员对你的尊敬。
建造者(builder)和设计者之间这种紧张的关系在建筑行业也看得到,只是在软件行业更加凸显而已。在软件行业之所以会如此强烈是因为和建筑行业有着一个关键性的差异。在建筑行业,设计师和工程师的技术有清楚的分别;但是在软件行业就分不太清楚了。任何在高度注重设计的环境中工作的程序员都必须具备熟练的技术,以便足够对设计师的设计提出质疑,尤其是当设计师对于新的开发工具或平台越来越不熟悉的情况下。
这些问题也许可以获得解决。也许我们可以解决人与人之间的紧张状态。也许我们可以加强设计师的技术来处理绝大部分的问题,并且制订出一个修改设计图的过程准则。但是仍然有另外一个问题:变更需求。变更需求是软件项目中最让我感到头痛的问题之一。
处理变更需求的方式之一是做灵活的设计,以便当需求有所变动时,你就可以轻易的变更设计。然而,这需要你对将要发生的变动有深刻的洞察力。一项预留处理易变性质的设计可能对于可预知的需求变更有所帮助,但是对于无法预知的需求变更却没有帮助(甚至有害)。所以你必须对需求有足够的了解以隔离易变的部分,照我观察,这是非常困难的。
部分有关需求的问题被归咎于对需求的了解不够清楚。所以很多人开始专注于研究需求工程过程(requirements engineering processes),希望得到更准确的需求以避免后面对设计的修改。但是即使朝这个方向去做一样无法解决问题。很多无法预知的需求变更起因于业务的变化。这是不能避免的,即使你小心翼翼的使用着需求工程过程。
以上问题使得planned design听起来像是不可能的实现的。无疑,这是很有挑战性的。但是我不认为planned design比使用”code and fix”方式的演进式设计差。甚至,我更喜欢 planned design。因为我了解 planned design 的缺点,并且正在寻找更好的方法。
XP实践(The Enabling Practices of XP)
XP因为许多原因而备受争议,其中最关键的原因就是它主张演进式设计而不是planned design。正如我们知道的,演进式设计可能因为特定的设计策略(ad hoc design decisions)和照成软件开发混乱而行不通。
理解这个争议的关键在于软件变更曲线(software change curve)。变更曲线说明,随着项目的进行,变更所花费的成本呈现指数的增长。变更曲线按照不同的阶段可以表示为:在分析阶段花一块钱所作的变更,放在编码阶段中则要花费数千元。讽刺的是大部分的工程仍然没有分析过程而以非正式的方式进行着,但是这种成本上的指数关系还是存在着。变更曲线意味着演进式设计可能行不通,它同时也说明了为什么planned design要小心翼翼的进行,因为在planned design中产生的任何错误同样会导致指数增长。
XP的基础是建立在将变更曲线拉平,使得演进式设计可行的假设上的。XP将变更曲线拉平而又为自己所用。就XP中一系列相互依赖的实践来说:你不能仅仅使用那部分以平滑曲线为前提的XP利用实践(exploiting practices)而放弃那些建造平滑曲线这个前提的XP启动实践(enabling practices) [译注1]。这就是争论的来源,因为很多人不了解这其间的关系。通常评论家们提出的批评,是基于自身对XP的体验,但是他们割裂了XP中相互依赖的实践,结果可想而知,于是对XP的印象也就这样了。
XP中有许多启动实践,其中最重要的是测试(Testing)和持续集成(Continuous Integration)。如果没有测试提供保障,其它的XP实践都将变得不可行。持续集成可以保持团队成员信息同步,这样当你修改代码时,不必担心与其他团队成员集成会有问题。协同运用这些实践可以很大的影响变更曲线(change curve)。这让我再次想起在ThoughtWorks引入测试和持续集成之后,明显的减轻了开发工作量(development effort)。这的确令人质疑是不是必须像XP所主张的那样,要用到所有的实践才能得到大幅改善。
重构(Refactoring)具有类似的作用。那些采用XP建议的原则对程序代码进行重构的人发现,这么做要比无章法或是特殊方式的restructuring明显更有效率。这也曾是Kent指导我进行适当的重构得到的难忘经验。也因为这一次巨大的转变促使我以重构为主题写了一本书。
Jim Highsmith写了一篇很棒的文章《XP概要(summary of XP)》,文中他把planned design和重构模拟作了天秤的两端。假设你不会随后变更需求,那么在大多数传统方法中planned design占有优势。但是当需求发生变更,则重构占有了优势,它可以大幅降低变更带来的成本。当然我们不能完全抛弃planned design,而是采用一种保持“天平”平衡的协作方式。就我个人理解是:我会一次做完所有的设计然后不断的重构。
持续集成、测试和重构这些启动实践让演进式设计变得可行起来。然而我们尚未找到其间的平衡点。我相信,不论外界对XP存有什么样的印象,XP不仅仅是测试、编码和重构。在编码之前还是需要进行设计。一些设计要在所有的编码开始之前进行,而大部分的设计直到迭代中要实现具体功能时才进行。但是预先设计和重构之间要找到新的平衡。
简单的价值(The value of Simplicity)
XP大声疾呼的两个口号是“考虑能够工作的最简单的事情(Do The Simplest Thing that Could Possibly Work)”和“你将不需要它(You Aren't Going to Need It)” (就是YAGNI)。它们都表达了XP提倡的实践——简单设计(simple design)。
YAGNI的意思是现在不要为了将来可能用到的功能编写任何代码。表面上好像很简单,但是问题出在像框架(framework)、可重用组件和灵活性设计上,这些东西本来就很复杂。你预先付出额外的成本去构建它们,以期望以后的开发中会节省成本。事先构建灵活的预先设计被认为是高效软件设计的关键部分。
但XP的建议是,不要以需要某项功能为理由来构建灵活的组件及框架出来。让整体架构随着需要成长。假如我今天想要一个可以处理加法但是不用乘法的Money类,我就只在Money类中构建加法功能。就算我确定下一次迭代中确实需要乘法运算,而且我知道实现会很简单。我还是会留到下一次迭代中再去做它。
其中一个理由是经济效益。如果我花时间在以后才需要的功能上,那就表示我没有将全部精力放在这次迭代中应该完成的事情上。已经发布的计划明确指明了现在要进行的工作,如果现在做将来才需要做的事情,就违背了开发人员和客户之间的协议。这种做法使得完成当前迭代过程存在风险。即使完成本次迭代所有素材不存在任何风险,要做哪些额外工作也是由客户来决定的——还是有不包括乘法功能的可能。
这种经济效益上的限制是因为我们有可能出错。就算是我们已经确定这个功能应该如何运作,都有可能出错——尤其是在我们还没有得到详细需求的时候。提前做一件错误的事情比提前做一件对的事情更浪费时间。而且XP专家们通常认为我们更有可能做错而不是做对(我心有戚戚)。
第二个支持简单设计的理由是复杂的设计要比简单的设计令人难懂。因此随着系统复杂度的提高,对系统进行任何修改变得越来越难。如此,若系统必须加入更复杂的设计时,成本势必增加。
现在很多人发现这个建议是无意义的,其实他们这样想是对的。因为你所想象的普通开发并没有采用XP启动实践。然而,当planned design和演进式设计之间的平衡有了变化时(也只有这时),YAGNI就会变成好的实践。
总之,你不要花费任何精力在以后迭代中才需要的功能上。即使实现这个功能的成本可以忽略,你也不要这样做,因为这样会增加修改成本。但是你只有在使用XP或者其它类似的降低变更成本的技术时才可以遵循以上建议。
究竟什么是简单(What on Earth is Simplicity Anyway)
因此,我们希望程序能够越简单越好,这听起来没什么好争论的,毕竟有谁想要复杂呢?但问题来了,究竟“什么是简单呢?”
在XPE一书中,Kent对简单系统制定了四个评测标准,依序是(最重要排最前面):
l 通过所有测试(Runs all the Tests)
l 呈现所有的意图(Reveals all the intention)
l 避免重复(No duplication)
l 最少数量的类或方法(Fewest number of classes or methods)
通过所有测试是一项相当简单的标准,避免重复也很明确,尽管许多开发人员需要别人的指点才能做到。比较麻烦的是“呈现所有的意图”这一项,这到底指的是什么呢?
这句话的本意就是明确的代码。XP对代码的可读性有着极高的重视。虽然在XP中,术语"clever code"已经被滥用,不过意图清楚的代码,对其他人来说是友善的(cleverness)。
Josh Kerievsky在XP 2000论文中举了一个很好的例子。他阅读了在XP领域可能是大家最熟知的JUnit的程序源码。JUnit使用装饰模式(decorators)在测试用例(test cases)中加入可选功能,像是并发同步和批量”set up”代码。将这些可选功能的代码抽取到装饰者(decorator)中,使得通用的代码更加清晰。
但是你必须扪心自问,这样做之后的程序真的简单吗?对我来说是,因为我熟悉装饰模式。但是对于不了解装饰模式的人来说还是相当复杂的。类似的,JUnit使用的可插入方法(pluggable method)对大部分刚开始接触的人来说根本不简单清晰。所以,也许我们可以说JUnit的设计对有经验的人来说是比较简单的,但是对于新手来说则比较复杂。
XP的“一次,并且仅有一次(Once and Only Once)”和《程序员修炼之道》提倡的DRY(不要重复自己)都专注在去除重复的代码。这些建议都有很显著而且惊人的效果。只要依照这个方式,你就可以避免重复。但是它不能解决所有问题,简单化还是不容易做到的。
最近我参与了一个可能被过度设计的项目。系统经过重构之后去除了部分灵活性设计。就像其中的一位开发者说的那样“重构过度设计的系统要比重构没有设计的系统容易的多”。做一个比你所需要的简单一点的设计是最好的,但是稍微复杂一点点也不是什么严重的事情。
我听过最好的建议来自Bob大叔(Robert Martin)。他的建议是不要太在意什么是最简单的设计。毕竟以后你可以,应该,也会再重构它的。最后,不断的重构,比知道怎样才能做到最简单的设计重要得多。
重构违反了YAGNI吗?(Does Refactoring Violate YAGNI?)
这个主题最近出现在XP邮件列表中,当我们审视设计在XP中扮演的角色时,我觉得很值得拿出来讨论。
基本上这个问题起因于重构需要耗费时间却没有增加新的功能。而YAGNI的观点是你应该为了眼前的需要做设计而不是未来,这样算是相互抵触吗?
YAGNI的观点是不要加入一些现阶段不需要的复杂性,这也是简单设计这条实践的部分精神。重构可以保证你的设计尽可能的简单,所以当你觉得可以让系统变得更简单的时候,就进行重构。
简单设计这条实践不仅仅是XP利用实践而且也是XP启动实践。只有基于测试、持续集成和重构才能有效的保证简单设计。而同时,简单设计又对于保持变更曲线平缓非常重要。任何不必要的复杂性都会让系统变得难于调整,除非这个调整正是你加入复杂性所预料的调整。不过,人们通常不善于预料未来,所以最好还是努力地保持简单性。同样,人们也不太可能第一次就能做到最简单,因此你需要重构来帮助你更接近这个目标。
模式与XP(Patterns and XP)
JUnit的例子让我不得不想到模式。XP和模式之间的关系很微妙,也常常被问起。Joshua Kerievsky认为模式在XP被过分轻视,而且他所提出的理由也相当令人信服,我不想再重复。不过值得一提的是,对于很多人来说模式似乎与XP是有冲突的。
其实本质在于模式常常被过度的使用。世上有太多传奇的程序员,第一次读到四人帮以32行代码阐述16种模式这样的事情还记忆犹新。我还记得有一晚与Kent喝着酒一起讨论一篇叫做《不要设计模式:23种简单技巧》的文章。我们认为像文章中那样的情况使用if语句要好过策略模式。这个玩笑说明模式常常被滥用,但这并不表示模式是不足取的。问题在于你要怎样运用它。
其中一种观点是简单设计的力量自然会将设计引向模式。很多重构的例子明确地这么做了;甚至不用重构,你只要遵从简单设计的规则就会发现模式,即使你还不知道模式是什么。这种说法也许是真的,不过它真的是最好的方式吗?当然,如果你对模式有个大略的了解,或者手边有一本书可以参考,而不是自己去发明模式,那这是一种较好的方式。当我觉得快用到模式的时候,我肯定会去翻翻GOF的书。就我来说,有效的设计告诉我们,模式值得付出代价去学习——这是它特有的技能。同样地就像Joshua所建议的,我们需要更熟悉于如何逐步地运用模式。XP使用模式的方式与一般使用模式的方式有所不同,但并没有抹煞它的价值。
但是从一些邮件列表中的观点看来,我觉得很多人认为XP并不鼓励使用模式,尽管XP的大部分倡导者也都是模式运动的领导者。是因为他们已经超越了模式?或是因为他们已经将模式融入了思想而不必再去体现它?我不知道其它人的答案是什么,但是对我来说,模式仍然非常重要。XP也许可以认为是一种开发过程,而模式则是设计技术的骨干知识,不管采用哪种开发过程,这些知识都是很有用的。不同的过程使用模式的方式也许不同,XP强调直到需要时才使用模式并且通过简单的实现逐步引入模式。所以模式仍然是一种必须获得的关键知识。
我建议采用XP的人这样来使用模式:
l 花点时间学习模式。
l 留意使用模式的时机(但是别太早)。
l 先关注如何以最简单的方式使用模式,然后再慢慢增加复杂度。
l 如果你用了一种模式却觉得没有多大帮助——不用怕,再次把它去掉。
我认为使用XP应该更强调学习模式。我不确定如何让它和XP的实践合适的搭配起来,不过我相信Kent会想出办法来的。
发展架构(Growing an Architecture)
软件架构是指什么呢?对我来说,架构是系统核心元素(elements)的意思,这部分是难以改变的。剩下的都必须建造在这基础上。
那么当你使用演进式设计时,架构又扮演着什么样的角色呢?XP的批评者再一次地声称XP忽视架构,因为XP的路线是尽快地开始写程序,相信重构会解决所有设计上的问题。有趣地是,这些批评者说得没错,这有可能是XP的缺点。的确,最激进的XP专家——像Kent Beck,Ron Jeffries和Bob Martin——尽其所能地避免任何预先的架构性的设计。在你真的要用到数据库之前,不要加入数据库,先用文本文件来代替,在之后的迭代中通过重构加入数据库。
我常被认为是一个胆小的XP专家,这点我不同意。我认为一个概括性的初始架构有它的用处所在。像是一开始要怎么将应用分层,如何与数据库交互(如果你需要的话),使用哪种方式去处理web server。
实际上,我认为这些就是近年来我们所研究的模式。随着你对模式认识的加深,你会越来越习惯于在开始时就考虑如何去适当的使用它们。不过,关键性的差异在于这些初始架构的决定是可以更改的,明确地说只要团队意识到他们的判断有误时,就应该有勇气去修正它们。有人跟我讲过一个关于项目的故事,就在项目接近部署阶段时,他们突然决定不再使用EJB,并且要将已有的应用从系统中移除。这是一个规模相当大的重构,不过最后还是顺利的完成了。XP中的启动实践不仅让事情变得可能,而且很值得去做。
如果以相反的方式来做这件事呢?如果你决定不采用EJB,那么将来会很难加入吗?你是否真的要在尝试过各种方法却发现依然欠缺什么时,才使用EJB?这是一个牵涉很多因素的问题。不使用复杂的组件当然可以增加系统的简单度,而且可以让项目进展得比较快。但有时候从系统中抽掉某个部分会比加入它要容易多了。
所以我建议从评估架构可能的样子开始。如果你看到将会有多个用户操作大量的数据,那么一开始就应该直接使用数据库。如果你看到很复杂的业务逻辑,那么就套用领域模型(domain model)。当你怀疑是否偏离了简单性原则时,那就遵循YAGNI的精神。所以你要有所准备,当发现所使用的架构没有任何帮助时应尽快的简化它。
UML与XP(UML and XP)
在我投身于XP领域之后,我和UML间的关系使得一个很大的疑问一直挥之不去:这两者能兼容吗?
它们之间有一些不兼容的地方。XP显然在很大程度上不再重视画图。虽然官方的立场是“有用就用”,但是实际上却隐藏着“真正的XP实践者不需要画图”的潜台词。而且确实有很多人不习惯画图,就像Kent一样,这加强了这种观点。确实,我也从来没见过Kent主动使用固定的标记法画下软件蓝图(software diagram)[译注2]。
我觉得这个问题有两个独立的原因。第一个原因是软件蓝图对一些人来说有用,而对另一些人来说没用。有危害的是,认为软件蓝图有用的人却不是真正动手做的人,反之既然。我们应该接受并不是每个人都应该使用图表(diagram)的事实。
另一个原因是软件蓝图常倾向于引入繁重的过程(heavyweight process),这些过程耗时费力却不见得有用,甚至还会产生负面影响。我认为应该教导人们如何适当有效的使用图表并且避免落入繁重的陷阱,而不是像些XP专家说的那样“只有懦弱的人才用”。
所以,我对于适当有效的使用图表的建议是:
首先别忘了你画这些图的目的,主要的价值在于沟通。有效的沟通意味着突出重要的部分而忽略不太重要的部分。这样的取舍也是有效运用UML的关键。不必把全部的类(class)都画出来——只画出重要的就可以了;对于每个类也不必显示所有的属性(attribute)和操作(operation)——只显示重要的;也不要为所有的用例(use case)和方案(scenarios)画时序图——只... 让你了解大概的情况。在使用图表时常犯的通病就是人们通常希望详细完整的把图表现出来。其实程序代码就是提供完整信息的最佳来源,同时代码本身也是保持信息同步的最简单方式。面面俱到的图表是一目了然的敌人。
图表通常的用途是在开始编写代码之前探讨设计。在你的印象中常常觉得这样的行为在XP中是不合法的,但并不是这样的。很多人都说当你遇到棘手的问题时,是值得先将它们汇总起来开一个快速设计会议(a quick design session)。当你进行设计会议时:
l 保持简短。(keep them short)
l 不要涉及到所有的详细(只挑重要的)。
l 把结果当作是草图,而不是定案。
上面的最后一点值得深入探讨。当采用了预先式设计,随后而且往往是在编码的时候,你会不可避免的发现一些设计错误。如果你适时变更设计,它就不是问题。麻烦的是当人们认为设计已经定案时,他们不会将在编码中获得的知识反馈到设计中去。
变更设计不代表一定要更改图表。画这些图表来帮助你了解设计,然后就丢掉,这么做是非常合理的。这些图能有所帮助就有它的价值了,它们不必永远存在。最好的UML图是不会作为历史资料存在的。
不少XP实践者使用CRC卡片,这与UML并不冲突。我总是将CRC卡片和UML图混在一起,哪个更适合手头上的工作就选择哪个。
UML图的另一个用途是作为持续维护的文档资料。它一般的形式,就是在用例工具(case tool)中看到的模型。最初的想法是留着这样的资料有助于构建系统。事实上却常常没什么用。
l 保持图表更新花费太多的时间,因此常无法与代码保持同步。
l 它们被隐含在用例工具或者厚重的包装(a thick binder) 中,以致被人忽略。
所以,希望这种持续维护的文档资料起到作用,就要从这些已知的问题下手:
l 只用一些改起来不至于让人觉得痛苦的图。
l 把图放在显眼的地方。我喜欢画在墙上,鼓励大家一起动手修改。
l 注意这些图是不是有人在用,没用的就擦掉。
使用UML图的最后一种方式是作为移交工作时的文档资料,比如说在不同团队移交工作时。按照XP的观点,产品的文档就是素材(story),因此这些文档的价值已经得到了客户的肯定。于是UML又派上了用场,它所提供的图形有助于沟通。别忘了程序代码本身就蕴含了所有详细的信息,所以图形的作用只是提供概括以及突出重要的部分。
关于隐喻(On Metaphor)
好吧,我不妨公开的承认——我一直没有抓住隐喻(metaphor)的精神。我知道它有用,而且在C3项目中运用得很好,但是这并不表示我知道怎么用它,更不用说要解释怎么用了。
XP实践中的隐喻是建立在Ward Cunningham's为系统命名的做法上。提出一些众所周知的常用词汇,然后用这个词汇表(vocabulary)来比喻整个领域(domain)。这些代表系统特性的词汇会出现在类和方法的命名上[译注3]。
我曾经通过建造领域概念模型(conceptual model)来构造名字系统(system of names)。利用UML或者它的前身与领域专家一起建造概念模型。我发现你必须很小心的保持最精简的符号集合,而且要当心别让技术性的问题不知不觉的影响了这个模型。但是一旦你完成这个概念模型,你就可以为这个领域建立一个词汇表。这些词汇很容易理解。领域专家可以用来与开发人员沟通。概念模型无法与类设计完美的吻合,但是已足够给整个领域一个通用的词汇表。
目前我找不到任何理由说明为何这个词汇表不能运用比喻,就像C3中将工资单比喻为工厂装配线一样;我也不觉得基于领域词汇表建造你的名字系统有什么坏处。我也不会放弃我可以驾轻就熟的系统命名方式。
人们常批评XP是觉得一个系统至少需要一些笼统的设计。XP实践者们则常以“就是隐喻啊”来响应。但是我一直没有看到一个对于隐喻令人信服的解释。这是XP的空缺,需要由XP实践者们来理出头绪。
你想成为架构师吗?(Do you wanna be an Architect when you grow up?)
近几年来“软件架构师(software architect)”越来越热门,这是一个就我个人而言难以接受的术语。我太太是建筑工程师。工程师和架构师之间的关系是... 有趣的。我最喜欢的一句话是:架构师喜好三种东西:球状物、灌木丛和鸟。因为架构师画出这些美丽的图画,却要工程师保证能全都做出来。所以我避免使用软件架构师一词,毕竟如果 连我的 太太都不能尊重我的专业,我又怎么能对其他人有所期望呢?
在软件行业,架构一词可以代表很多含义。(软件行业中几乎所有的词都可以代表很多含义。) 概括为一句话是:我不是一名程序员——我是一名架构师。还可以进一步解译成:我现在是一名架构师——我这么重要怎么能参与编码。然后这个问题就变成了:是否一定要彻底脱离编码,才能与你的技术领导者的地位相符合?
这个问题引起众多的不满。大家一想起再也无法担任架构师这个角色就非常生气。我经常听到这样的埋怨:在XP中没有给经验丰富的架构师挥洒的空间。
就设计本身来说,我不认为XP不重视经验或者好的设计技术。事实上,我从很多XP提倡者——Kent Back、Bob Martin当然还有Ward Cunningham——那里学到了设计的理念。然而这也代表着他们的角色从大家既有的印象中开始转变成为技术领导者。
我将以一位ThoughtWorks的技术领导者Dave Rice为例。Dave曾参与过几次完整地开发,并且曾在一个50人的项目中担任非正式的技术主管。他担任主管的角色意味着要花很长的时间与程序员为伍。他会和需要帮助的程序员一起工作,否则就留意着看谁需要帮助。他的座位上有一个明显的标记。在ThoughtWorks很长一段时间,他可以适应任何形式的办公环境。他曾经与发行经理(release manager)Cara共享办公室一段时间。而在最后的几个月,他搬到了程序员们工作的开放式房间(就像XP实践者喜欢的开放式“战斗场所”)。这么做对他很重要,因为他可以知道事情的进展,并适时伸出援手。
熟悉XP的人已经意识到我描述的是XP中的教练(Coach)角色。的确,在XP玩的文字游戏中将技术领导叫做教练。意义在于:在XP中技术领导者的作用是通过教导程序员和帮助他们做决定而体现出来的。这需要良好的人际关系以及高超的技术。Jack Bolles在XP2000上说:孤立的大师只会作茧自缚,合作和教导才是成功的关键。
在研讨会的晚宴上,我和Dave与一位对XP持反对观点的人谈话。当我们讨论到以前的经验,我们的方法相当的类似。我们都偏好自适应的(adaptive)和迭代式(iterative)开发,也认为测试是重要的。所以我们对他反对的立场感到疑惑。然而当他说“最后的时候我会让程序员照着设计进行重构”。事情一下子明朗起来。后来Dave又对我解释“如果他不信任他的程序员,又何必要雇用他们呢?”,观念上的隔阂就更加清楚了。在XP里头,有经验的开发人员所能做的最重要的一件事就是尽量将所有技术传递给更多的新手。你让教练来指导开发人员做出重大的决定,而不是由一个架构师来决定这一切。就像Ward Cunningham指出的那样,这么做使得他的技术得到广泛使用,这对项目的好处大于一个孤胆英雄所能做的。
可逆性(Reversibility)
在XP2002大会上,对于敏捷方法(agile methods)和精益生产(lean manufacturing)之间的关系,Enrico Zaninotto发表了一场令人陶醉的演说。按照他的观点,他认为两种方法中都有一个关键特征:它们都通过减少过程中的不可逆性(irreversibility)来降低复杂度
从这个观点来看,造成复杂度的主要因素之一是在项目中做出了不可逆的决策。如果你可以轻易的改变你做出的决策,这意味着让决策恢复正常不会对项目有太大的影响——你的生活变得简单多了。所以,在演进式设计中设计师们应该想方设法避免在设计中产生不可逆性。与其急于尝试着去做出一个正确的设计,倒不如从下面两条路中选择一条:推迟设计(直到你有了足够多的信息)或者做出一个让你在不远的将来可以很容易推翻逆转的设计。
支持可逆性的决定就是敏捷方法强调使用源代码管理系统的原因之一。虽然它不能保证可逆性,特别是对很久以前做出的决策,但是它为团队提供了一个可以信赖的基础,即使很少去使用它。
可逆性设计也包含着一个可以很快发现错误的过程。迭代开发的一个重要作用就在于快速的迭代过程允许客户看着系统逐步成长,并且如果在需求中发现错误,可以很快地确定问题并做出修改,而不至于将错误堆积到让人望而却步的地步。对于设计来说快速的定位错误同样重要。这意味着你要对设计做一些装置(set things up),以便对存在潜在问题的部分做出快速的测试。这还意味着值得通过原型来模拟系统的一部分,以便试验对设计做出变更的困难度,即使你现在还不需要真的去变更。几个小组通过在原型中较早的试验变更,来评估对设计做出变更的困难度。
设计自律(The Will to Design)
我已经在这片文章中提到了很多技术实践,而人的因素则太容易被忽略了。
演进式设计在工作中需要驱动它集中于一点的力量。这个力量仅仅能够来自于团队中那些能够保证高水平设计的人员。
设计自律并不是来自于团队中每个人(尽管如果是这样会很好),通常在团队中只有一到两个人对全部的设计负责。这往往被认为是架构师担当的责任。
对设计负责就意味着要一直监视着代码,注意代码是否开始变得肮脏,并在局面失去控制之前采取快速的行动进行纠正。负责审查设计的人员并不一定要参与修改——但是他们一 定要确保有人来修改。
缺少自律的设计被认为是演进式设计失败的重要原因。即使人们对我在这片文章中提到的实践非常熟悉,没有自律的设计还是无法成功的。
很难重构的东西(Things that are difficult to refactor in)
我们能用重构来处理所有设计方面的决策吗?或者,是否存在一些问题因为弥漫在整个系统中而难以在将来加入设计中?XP的正统观念是:当你需要时,任何事情都可以很容易的加入,所以YAGNI总是能够适用。我想知道如果存在意外情况呢?有一个不错的例子是软件的国际化问题。这是不是一种现在应该立即进行,否则以后再加入时会觉得痛苦的事情?
我能很容易想到一些会陷入这种境地的事情。然而事实上我们仍然了解的太少。如果你必须加入一些功能,如国际化,那么随后你就会十分清楚它要花费多少成本。但你不容易在真正应用它之前就搞清楚,周复一周的完善和维护它要花费多少成本。同样,你也不容易意识到你可能已经做错了,这样无论如何也要做些重构了。
部分能够为YAGNI辩护的理由是,许多潜在的需求到最后并不真的需要,至少不是你预料的那种方式。不过对潜在的需求不做任何事情能节省下的力气,远没有通过重构来达到实际需要花费的力气多。
另外一个要想的问题是你是否真的知道如何去做。如果你已经有做过几次软件国际化的经验,你会知道该使用什么样的模式。同样的,你更可能取得成功。如果你对将要加入的构件(structures)有一定的经验,则多半会比第一次处理这种问题效果好的多。所以我的建议是,如果你知道应该怎么去做,你就要考虑现在做和将来做,两种情形之间不同的成本。然而,如果你没有处理过类似的问题,不仅是你无法正确评估需要的成本,而且你也不太可能把事情作好。这种情形,你就要选择将来再做。如果你在将来的某一天做了,而且尝到了苦头,则你要知道这比在早期加入它的情况好的多了。当你的团队更有经验,你对相关领域有更多认识,你对需求也更了解。通常到这时你回头看才会发现事情有多简单。提早加入的设计比你想象中要难多了。
这个问题也跟素材的顺序密切相关。在Planning XP一书中,Kent和我公开的指出我们的歧见。Kent偏向于只让业务价值这一个因素影响素材的顺序。在最初的争论过后,Ron Jeffries也同意了这种想法。我仍保持怀疑。我认为应该在业务价值和技术风险之间找一个平衡点。基于这样的理由让我提前为软件国际化做准备以降低风险。但是这种做法只有当第一次释放版本就需要将软件国际化时才能成立。尽快地释放版本是非常重要的。任何在第一次释放版本中不需要的附加复杂性,值得在第一次版本释放后再做。发行之后运行的程序有强大的力量,它抓住了客户的注意力,增加了信任感并且是一个学习的好机会。要尽你一切努力来靠近第一次发行的日期。即使在初次版本释放之后添加某些功能会花费更多的成本,还是要保证提早释放版本。
与任何新技术一样,XP很普通,以至于它的拥护者都不太清楚它的使用限制条件。许多XP实践者被告知演进式设计是不可能用于解决确定的问题,不料竟发现这是完全可能的。征服了“不可能”的情况,于是认为所有这一类问题都可以解决。当然我们不能笼统的概括XP的使用界限。直到XP社团偶尔碰到了界限并且惨遭失败,我们才能确信这是XP的使用界限。而且我们有权利去尝试超越这个潜在的界限。
(在Jim Shore近期的文章中讨论了一些问题,其中就包括国际化问题,这个潜在的界限最终被清除了。)
产生设计了吗?(Is Design Happening?)
演进式设计的一个难点是,很难分辨出设计是否已经发生。将设计与编码混合在一起有不去设计就编码的危险——这是造成演进式设计发散和失败的情形。
如果你在开发团队中,你就可以按照代码的质量来判断是否产生了设计。如果代码变得越来越复杂、维护越来越困难,这说明缺少足够的设计。但是遗憾的是,这只是主观观点。我们还没有一套可靠的标准来客观的衡量设计的质量。
如果缺少能见度(visibility)对于技术人员来说是困难的,那么对于团队中的非技术人员来说则是更让人担忧的。如果你是一名经理或者一名客户,你会怎么看待设计良好的软件?你应该认为是重要的,因为设计糟糕的软件将来变更的成本太昂贵了。到底什么是不好的设计,这很难回答,但是下面给出了一些提示:
l 和技术人员交谈。如果他们抱怨做出变更非常困难,那么请重视这个问题并且给他们修改的时间。
l 密切注视有多少代码别遗弃掉。在重构正常的项目中,被删除的代码数量会比较稳定。如果在一段时间内没有任何代码被删除,这无疑表示没有充分的进行重构——这会造成设计退化。然而,像所有的规则一样,这个可能被滥用,因此建议信任技术高超的技术人员胜于任何规则,尽管这是主观的。
这样来看设计死了吗?(So is Design Dead?)
设计死了吗?绝对没有,只是设计的本质发生了变化。XP的设计追求以下的技巧:
l 持续保持代码干净、简单。
l 重构,使你觉得任何有必要的时候都可以大胆的改进。
l 对模式有深刻的认识:不只死搬硬套,更要知道该何时用,以及如何逐步引入。
l 着眼于应付未来变化的设计,知道现在做出的决策可能要过后进行修改。
l 知道如何将设计传达给必要的人,用代码、图表和最重要的:交谈。
以上挑出来的技巧看来都挺吓人,但是要成为一个优秀的设计师本来就很难。至少我不觉得,XP让它变得简单些。但是我想XP让我们对有效率的设计有全新的看法,因为它让演进式设计成为一种可行的方式。而且我也很支持演进——否则谁知道我在干什么呢?
致谢(Acknowledgements)
过去的这两年里,我从很多好朋友身上偷学到不少好的想法,很多都已经记不起来了。但是我记得从Joshua Kerievski那里偷到的好东西。我也记得Fred George和Ron Jeffries给我很好的建议。我当然也不能忘记来自Ward和Kent的很多好的想法。
我也感谢曾经提出问题和指出打字错误的朋友。由于我的疏忽没有保留这些朋友的列表,但是我记得有Craig Jones,Nigel Thorne,Sven Gorts,Hilary Nelson,Terry Camerlengo。
修订记录(Revision History)
以下是这篇文章重大的修改记录:
2004年5月:添加了”The Will to Design”,”Reversibility”和”Is Design Happening”三个章节。
2001年2月:对growing an architecture、the role of an architect、和where things that are difficult to add with refactoring等段落做修改。
2000年7月:原始文件在XP 2000发表,并刊载于MartinFowler.com。
[译注1]:所谓XP利用实践(exploiting practices)是建立于软件变更曲线平滑的情况下的,而XP启动实践(enabling practices)则是XP建立这个基础的方式。由于不知道国内如何来翻译者两个名词的,所以姑且这样翻译吧。
[译注2]:软件蓝图(software diagram)的翻译有点牵强,其实本意就是表达软件设计意图的图表。
[译注3]:说到隐喻,记得以前我在做一个文件管理脚本程序时,就将不同责任的脚本文件比喻成不同的狗,负责遍历目录的叫做侦探犬,负责记录的叫做档案狗……