软件设计是否走到了尽头作者 Martin Fowler 译者 zhangjing[AKA] 在很多对极限编程浅层接触的人看来,似乎XP给软件设计判了死刑!在XP中,软件设计不仅被讥笑为"Big Up Front Design"。甚至一些软件设计技术,比如UML(统一建模语言)、灵活框架技术、模板设计等对于软件设计的态度也是模棱两可--既不重点强调,也不完全忽视。实际上,XP包含很多设计思想,只是这些设计思想和构建一个软件有所不同。XP恢复了进化设计的思想,允许进化成为一种可扩展的软件设计思想。同时,XP也对软件设计人员提出了新的挑战和技能--如何进行一种简单设计、如何利组件技术保持设计简洁、如何以进化方式运用模板设计。 本文重点阐述以下几个方面的问题: XP向很多传统的软件开发观点提出了挑战,其中争议最大的一点是XP并不强调"UP_FRONT"设计的重要,而是支持一种进化的观点。对它的反对者来说,这无疑就是"code and fix"时代的再现,就连它的爱好者也似乎都认为XP拒绝设计技术(比如UML)、原理和模板。无需担心软件设计,真正聆听你的代码,好的设计会翩然而至。 我发现自己处在这场争论的中心。我的很多同事都卷入了图形设计语言--统一建模语言UML、模板中并走在了前列,我本人也编写过有关UML和模板方面的书籍。那么,现在我赞成XP是否意味着我收回了我在这些书籍中的观点,意味着我丢弃了所有计数革命的观点? 我不想让你无所适从:两种答案--快捷的答案是"No",而详细的答案,就是下面这篇文章。 计划设计和进化设计: 本文中我将用两种方式来阐明怎样在软件开发中做好一个设计。最常用的也许就是进化设计,其本质就是系统被实现之后系统的设计还可以增加。设计是编程的一部分且程序包含了设计的更改。 一般情况下,进化设计是一个设计灾难。这种设计结束于一大堆出于ad-hoc式的战略决策的集合,每一次决策都使代码比原来更难以更改。你可以给出无数个理由认为这根本就是没有设计,何况这样的设计肯定是很糟糕的设计。就像Kent所指出的:设计的目的是从长远的观点使软件容易更改。没有好的设计,就没有办法进行有效的更改。你会感觉软件的状态越来越糟,设计的缺陷也越来越暴露。其结果不仅使软件难以更改,而且使Bug更易繁殖、更难发现、更难安全清除--"Code and Fix"噩梦!随着工程的进展,Bugs越来越难以定位修补。 计划设计不仅可以有效的对付以上问题,而且还包含了一种从其他学科分支工程继承的观点:如果你只是想建一个狗窝,你只需简单的把木头凑在一起,大致有个狗窝的形状就行。但如果你想建的是一幢摩天大楼,你就不能那样干--中途就肯定倒塌!你必须依照工程图纸,就像我的妻子在波士顿郊区的一间工程办公室所作的那样。做设计时她会考虑到所有的问题,有些运用数学分析,更多的是用建筑符号,建筑符号规定了你如何做好一个基于经验(有些基于数学)的结构设计。一旦做好设计,她的建筑公司就会把设计图纸交给另一家公司去建造这个摩天大厦。 软件的计划设计也应该遵照这种方式。设计者预先考虑软件工程中的大问题,他们不必参与代码编写,他们不是在建造软件而是在设计软件。软件设计者可以利用像UML这样的设计技术,从软件编码的细节问题中脱离出来,从而工作在更抽象的一层。因为设计者从整体上考虑,他们可以避免一系列的导致软件熵的技术决策。程序员可以遵照设计者的意图(假设他们遵照设计者的一图),很好的建造软件。 自从70年代提出计划设计的思想,很多人都尝试过。它在很多方面都优于"Code and Fix"式的进化设计。但它也有它的缺点。第一:利用这种方法,你不可能把写代码过程中可能碰见的所有问题都考虑到。写代码时你不可避免可能要质疑设计是否有问题。如果设计者查找设计问题并修改,想一想这个设计移植到别的工程中会怎样?程序员开始围着设计编代码,软件熵也就加了进来。如果设计者不做这个工作,那就得花时间找出设计的问题,更改设计图,然后修改代码,由于时间的压力尽快打个补丁!还是加进了软件熵! 更深一层,还存在技术文化的问题。设计者是因为有技术和经验才是设计者。但因为他们忙于设计,根本没有时间编写代码,但软件开发的工具和材料都发展的很快,你一旦不写代码,你不仅自己考虑不到工具改变所带来的技术变迁,也替实际写代码的程序员考虑不了太多。 这种设计者和建造者之间的冲突在建筑中也有,但是在软件工程中,因为关键的分歧,冲突更大。在建筑工程中,设计者和建造者在技术上有明显的分工,软件工程就不是这样。任何为好的设计而工作的程序员都应该是技术高手,都可以对设计提出质疑,尤其在设计者对日新月异的开发平台知之甚少的时候。 即使这些问题可以修补,或许我们可以解决人与人之间的冲突,我们也可以让设计师水平高到可以处理最大多数问题,也可以有一套规则足够完整用于改变设计图。但,还是存在另外一些问题:更改需求。这是我所遇见的软件工程中最让人头疼的大问题。 解决需求改变的方法之一是可以将设计做的足够灵活,需求改变时设计可以方便的随之改变。然而这需要理解透彻期望有什么样的需求改变。设计可以计划的留有扩展余地,但这仅仅对预见到的需求改变有帮助,对于没有预见到的需求改变不仅没有帮助,反而有害。因此,设计师必须充分理解需求,分离出哪些地方应该留有余地。我的观点:这非常困难! 现在,有关需求的问题就归结到没有明确理解需求的问题上来了。很多人在需求分析过程中集中精力以求对需求得到更好的理解,从而阻止在后期的设计时更改需求。但这也无法完全阻止需求的更改。商业活动的更改导致很多无法预见的需求更改。这些更改根本无法阻止,即使在需求分析过程非常仔细。 所有这些让计划设计听起来似乎是不可能的。是的,这些都是大的挑战,但是我还是不认为计划设计比"Code and Fix"式的进化设计更糟。我更喜欢计划设计而非"Code and Fix"。我已经认识到了计划设计的一些问题并在寻求新的解决方案。 XP的能动实践 对XP的争议很大,这有很多原因,其中最关键的问题是XP提倡进化设计而不是计划设计。我们都知道,进化设计有可能因为一些ad-hoc设计决策而无法工作,而且会带来软件熵。 理解这个争议的核心是软件变化曲线。软件变化曲线表明:随着工程的进行,做更改的的代价将呈指数增长。你在需求分析时花1美元可以完成的更改,在产品形成后再打补丁就得花上千美元,这也是软件变化曲线表达的意思。可笑的是很多工程仍然没有需求分析过程,只有ad-hoc过程,这个指数也依然存在。指数变化曲线意味着进化设计可能无法工作,也表明了为何计划设计应该设计的很仔细,因为计划设计中的任何错误也会导致同样的指数增长。 XP的基本设想是使软件变化曲线尽可能的扁平,从而让进化设计能够工作。XP使这种扁平化成为可能,同时这种扁平化又被XP开发利用。这是XP实践中结合实践的一部分:很明显,你不可能只开发利用扁平化软件变化曲线而不去做让扁平化成为可能的那部分事情,这也是XP的争议来源。很多人对不理解成为可能性的开发利用持批评态度。批评常常来自批评家自己的经验:他们不做使开发利用实践能工作的使扁平化成为可能性的工作,结果是他们一看见XP,气就不打一处来! 能动性实践包含很多部分,其核心部分是测试实践和持续集成。没有由测试提供的XP的安全性是不可能的,持续集成对保持团队的同步非常必要。你可以做你的软件更改而不用担心无法和别人的东西集成在一起。在ThoutWorks上我再一次提起:这些实践结合在一起,就会对软件变化曲线产生较大的影响。引入测试和连续集成对开发成果有很大提高,也足够你认真质疑XP的主张--你需要所有的实践来获得大的提高。 组件软件也由类似的影响。在XP的建议下按照一定原则组件软件的人会发现这样做比随便运用组件技术,经常是ad-hoc式的重新组织代码效率要高的多。这其中也有我的经验,是Kent教我如何正确的进行组件设计,正是这种强大的改变促使我专门为此写了一本书。 Jim Highsmith在他的优秀著作summary of XP中,用一组刻度做了类推分析。一个刻度盘中是计划设计,另一个刻度盘中是组件软件。大多数传统的方法中计划设计占主导是因为假设你以后不改变你的设计思想,随着更改的花费降低,你可以在后期对你的设计做更多像组件软件一样的改变。我们并没有完全丢弃计划设计,而是在两种设计方式之间寻找一种平衡,以使其协同工作。我的感觉--组件软件之前我在用一只手完成我的设计。 这种由连续集成、测试、组件软件组成的能动性实践,提供了一个新的环境使得进化设计大受欢迎。但是我们还没有计算出两种设计方式的平衡点在那里。我确信:尽管外界有压力,XP不仅仅是测试、编码和组件软件,在编码之前肯定有设计的空间。有些是在做任何编码之前,更多的是在为某以特定任务编码之前的反复过程。但在"up-front"设计和组件软件之间存在一个新的平衡点。 简单的价值 YAGNI经常被提起,意思是说明天需要增加的将来要用到的代码,今天不要把它添上。表面上听起来非常简单,但是考虑到框架,不确定成分以及灵活性设计,问题就会接踵而来。这些问题构建起来很复杂。要构建他们,就有额外的up-front代价,但你期望这个代价能在后来有所回报。这种构建灵活的up-front设计思想被认为是高效软件设计的关键。 然而XP的建议是:你不用在首次设计时就构建灵活的组成和框架,即使需要这个功能。让结构随着必须性增长。如果我今天只需要一个Money类处理加法而没有乘法的功能,那我只把加法功能添加到此类中,即使我确切知道在下一次返工时必须有乘法功能,我也知道很容易就能把乘法功能加进来而且很快就能完成,我还是把它留到下一次返工时再添加。 这样做的原因之一是出于经济考虑。如果我一定要做明天要求的将来才用到的工作,那将意味着我无法为本次代码反复而努力。版本计划就是只做现在需要的,为将来用到的东西努力,这与开发者和用户之间的协议是相违背的。如果本次返工的内容没有实现,就有一定的风险,即使本次返工的内容不承担风险,也应该由用户决定还有那些额外的工作要做--那也许还是不要求在Money类中实现乘法功能。 这种妨碍经济活动的行为通常还混合其他因素,比如我们根本就没有把明天要求实现的功能分析明白。虽然我们肯定知道要添加的函数如何工作,我们还是有可能出错--我们还没有详细需求说明。过早的为错误的见解而工作比过早的为正确的见解而工作要糟糕的多。XP专家认为我们总是错的可能大于对的可能(我本人也同意这一点)。 处在traveling light原理的反面是简单设计的第二个原因。复杂设计理解起来要比简单设计困难的多,对系统的任何修改都会给设计增加复杂性而变得更艰难。这就给添加更加复杂的设计和需要添加复杂设计之间这段时间增加了代价。 很多人认为XP的这个建议是废话。假设开发环境中没有合适的XP能动性实践,他们这样认为是正确的。然而,当计划设计和进化设计之间的平衡发生改变时,YAGNI将会成为一种好的实践(也只有那时)。 因此,总结起来,不要花费力气去添加一些以后的返工中才用到的新功能。哪怕没有任何代价,也不要想着这样去做,因为即使添加它没有任何代价,它也会增加软件更改的代价。但是,只有在使用XP或者类似可以降低软件更改代价的技术时你才有这种意识。 究竟怎样才是简单? 我们都想自己的代码尽可能的简单,听起来似乎简单的无可争议。毕竟,谁想让自己的代码编的复杂?但是问题是:什么是简单? 在XPE中,Kent给出了简单的四个标准,按重要性顺序罗列如下: 所有测试过程都能运行是一条恰当的简单准则。尽管很多开发者需要引导才能实现,无重复也是直截了当的简单准则。不太好理解的一条准则是展示设计意图,这一条标准的确切含义是什么? 这里,基本的评价是代码的清晰性。XP对可读性好的代码有很高的评价。在XP中,"clever code"是不受欢迎的。但是有些开发者展示设计意图的代码则是另外一种cleverness。 Josh Kerievsky在他的XP 2000论文中举了一个很好的例子。他着眼于大多数公开的XP代码-JUnit。JUnit装饰性的添加一些选择功能来测试某种情况,这些工作和别的编码工作同时进行。要把这些代码区分开来,就要求普通代码非常清晰,否则难以做到。 但是你必须自问:我的最终代码是否真的简单?我自己是这样做的,另一方面我对Decorator模板也很熟悉,只是它的很多东西很复杂。类似的,JUnit所利用的插入方法,我也注意到很多人在一开始并没有发现它的清晰。这样说来,我们也许可以总结性的说:JUnit设计对于有经验的设计者是简单的,而对于缺乏经验的设计者是复杂的? 我认为,在消除代码的重复性问题上,XP的"Once and Only Once"和Pragmatic Programmer的DRY(Don't Repeat Yourself)都是很好的建议。但它们毕竟不是万能的,仅遵循它们的原则会花费很长时间,而且简单性的寻求也是一件复杂的事情。 最近我专注于一些有充分理由可以称为over-designed的问题。Over-designed少了一些灵活性,还有组件设计的问题。正像一名开发者所说:设计over-designed的组件要比设计没有设计的组件容易的多。让事情变得比你所需要的简单终归是一件大好事,可让事情稍微复杂一些也并不见得就是灾难。 我从Uncle Bob (Robert Martin)那里听到了关于以上问题的最好建议:不要过于专注什么是简单的设计,毕竟你可以尽可能迟的进行组件软件。在最后时候组件软件的积极性要比知道什么是最简单的要重要的多。 组件软件和YAGNI相违背吗? 这个主题是随着最近的一些XP邮件而引发出来的,很值得提出。我们可以看看XP中设计扮演了什么样的角色。 这个主题产生的基本点是:组件软件要花费时间但并不增加任何功能。问题是YAGNI的观点是设计是为现在而不是将来设计的,这是不是相违背的? YAGNI的观点是不要添加现在不需要的东西去增加软件的复杂性,这也是简单设计的一部分。组件软件对保持软件设计的简单也很必要。所以,你可你进行组件软件来使设计简单一些。 简单设计既是XP的开发利用实践,也是XP的能动实践。只有当你经过了测试、连续集成、组件软件你才可以有效的进行简单设计。但同时保持设计的简单对保持软件变化的曲线的平坦也很有必要。除了你期望加入的设计的灵活性,任何不必要的增添都会使系统变得更加难以更改。可是大家一般都不擅长预期,所以把简单作为目标也是好办法。但是我们第一次又很难知道什么是简单,所以有序的组件软件就很必要了! 模板和XP 有了JUnit的例子,我不可避免的要谈到模板。模板和XP之间的关系非常有趣,也是一个公共的话题。Joshua Kerievsky认为XP中并不强调模板,且论据充分,在此我不准备重复这个问题。但还是值得指出:在很多人的意识中,模板和XP是矛盾的一对。 争论的本质问题时模板常常用得太多。编程高手很多,这些高手在第一次读GOF时很可能就丧失了新鲜感--那里面32行代码中包括了16种模板!记得有一天晚上,我和Kent喝了点酒。在麦芽糖的作用下,我们划掉了一篇名为"Not Design Patterns: 23 cheap tricks"的文章。我们认为这种东西只是IF性的表述而不是什么策略。这个故事中也有一个论点:模板是被用的太多,但那并不使人认为模板不好,关键是你怎么用它。 一个理论是:简单设计会迫使你走向模板。很多时候,组件软件就清晰的做到了这一点,但即使你不遵循简单设计的原则,你也可能在不知道模板的情况下提供模板,这可能是真的。问题是,这是不是最好的方法?如果你大概知道你要怎么走而且你是在透彻理解问题而不是自己发明问题,这肯定是比较好的方法。当感觉到需要模板时我还是要求助GOF的。对我来说,高效的设计就是我们要知道值得付出一个模板的价值--这是它本身的技能。同样的,就像Joshua所建议的,我们也必须对如何省心的、逐渐的进入一种模板非常熟悉。考虑到XP,我们用模板的方法和一些人有所不同,但我们并不抹杀模板本身的价值。 但是,阅读我最近收到的一些邮件,我意识到很多人看到了XP不提倡模板,尽管很多XP的支持者也是模板运动的领导者。这是因为他们看到了模板之上的东西,还是因为模板在他们的思想中潜入太深以至于他们在也无法认识它?我无法给出别人问题的答案,但对我来说模板依然很重要。XP可能只是开发的一个过程,但模块却是设计知识的支柱,无论你的开发过程会是什么样子。不同的开发过程会以不同的方式用到模板。XP强调不需要的时候就不用模板,也强调通过简单的实现把你的方法包含进模板。不管怎样,我们依然要承认模板是很关键的知识点。 我对于XPers运用模板的建议: 我认为XP应该更多强调对模板的研究。我自己不确定我能提出什么方法让模板适应于XP的时间,但我肯定Kent会。 UML和XP 从我开始研究XP以来,我一直反复思索UML和XP的联系。两者矛盾吗? 这两者存在几个矛盾点。XP肯定在一定程度上不强调图例。尽管办公室位置都沿着"use them if they are useful"线排列,还是存在这强烈的"real XPers don't do diagrams"口号。再加上像Kent这样看见图例就不舒服的人的存在,这种倾向更强烈。的确,我从来没见过Kent自愿以任何确定的符号画一种软件图例。 我认为问题的产生有两个原因。首先,有人认为图例有用,有人发现图例无用。问题是发现图例有用的人不一定用而发现它无用的人可能会用。或者我们可以认为有些人运用图例,有些人不用。 其次,软件图例有趋势变成一个庞大的过程。在这个过程中,你花费了很多时间去画图例,但这些图例可能对你没有帮助,更可能不利于软件开发。所以我想,应该给大家如何利用图例、避免陷阱的建议,而不是XPerts整天把"only if you must (wimp)"挂在嘴上。 这是我对于如何应用图例的建议: 首先,思想明确你构建图例的目标,基本上以信息为评价。有效的信息是指选择重要的,忽略不重要的。对UML图例来说这种选择很重要。不要画每一个类图--只画重要类的类图,对每一个类,不用示出所有的属性和操作--只示例出重要的。不要为所有的用例和方案都画出顺序图。一般应用图例最常见的问题是很多人试图把自己的意思在图例中表达清楚。代码是最好的可理解信息,因为代码是最容易保持同步的。对图例,情况完全不是这样。 图例常见的应用就是在编代码之前做设计上的探究。你可能经常有这样的印象:这在XP中是不合法的。事实不是这样的。很多人认为,当你的任务模糊不清时,大家聚在一起开一个简短的设计会议是很值得的。这个会议应该是:
最后一点很值得扩展。当你做up-front设计时,你肯定会发现设计上某些方面的错误,而且只有在编码过程才能发现。假设你此时更改设计,那就没有什么问题。问题是人们一般认为设计既然做了,那就很不愿意在有编码过程回到设计。 更改设计并不一定意味着更改图例。在设计阶段画出图例帮助理解,然后完全可以丢弃图例。画出图例有所帮助,这就是图例的价值。它们不应该成为永久的人造物。最好的UML图例就不是人造物。 很多XPers应用CRC卡,这和UML并不冲突。我经常把CRC和UML混合使用,两种工具对于手边的工作都很有帮助。 UML图例的另一个用途是可以作为活动文档。一般情况下,它是寄居在辅助工具中的一种模板。有中观点认为这种文档有利于大家在整个系统上工作。事实上,它没有任何帮助。 UML的最后一个应用是用于提交文档,比如一个小组和另一个小组交接。XP的观点认为,产生文档和其他所有一样只是一种阶段性故事,它的价值取决于用户的需求。这里,UML就很有用了。假设图例是选择性画出的,便于交流,记住代码是详细信息的仓库,图例就是重要问题的总结和突出。 一个Metaphor 好了,我在公开场合也说过类似的话--我还是没有领会这个metaphor,我看见它在工作,而且在C3工程上工作的很好,但这并不是说我就知道怎么用它,更不用说如何解释怎么用它。 XP的Metaphor实践建立在Ward Cunninghams的名字体系方法的基础上。观点是你提供一套知名的名字,这些名字在这个领域的论坛中起字典作用,起作用的方法就像你在系统中给类和方法命名。 我通过构建这个领域的概念模板构建了一个名字体系,是利用UML或者它的预处理器和本领域的专家一起完成的。我发现在做这件事情的时候要很细心,要保持尽可能小的一个符号集,而且要防止把任何技术问题带到模板中。一旦做了,我还是发现可以用它构建一个词典,本领域的专家可以理解该词典并用它和开发者进行交流。该模板和类设计匹配的不是很好,但是足够在整个领域起到字典的作用。 我现在还没有看见这个字典不能成为一个比喻性字典的原因,就像C3比喻把薪水册变成了工厂的组装线。但是我也没有看见在这个字典基础之上构建你的名字体系有多糟糕,我也不愿意放弃对我来说能很好的得到一套名字体系的技术。 人们经常基于这个原因批评XP--你总得拿出一个系统的概要设计吧!XPers经常这样回答:"that's the metaphor"。我不认为对metaphor的解释很令人信服。这是XP的一个真正的缺陷,一个XPers应该重视的缺陷。 设计真的走到了尽头? 没有这么绝对,但设计的本质的确改变了。XP期望设计有以下技术特点: 这是一个可怕的技术选择,但是要成为好的设计师就得坚强。XP的确没有让事情变得容易,至少对我来说是这样。但我认为XP给我们提供了一种新方法去考虑高效的设计,因为它使进化设计在一次大受欢迎。我本人是进化设计的超级爱好者--谁能说不是这样? 致谢 过去两年离我从很多人那里拾取到,也窃取到很多好的想法和观点。其中很多都丢失在我朦胧的记忆里。但我依然清晰记得来自Joshua Kerievski的好想法。我也记得来自Fred George 和Ron Jeffries的有益的评论,当然,我也没忘记从Ward 和 Kent那儿弄到的很多好主意。 我非常感谢那些对本书排版印刷提出问题的人。在此非常感谢Craig Jones,让我知道书中少了一个"."。 |