第7章 什么是敏捷设计
可以使用许多不同的媒介描述设计,但是设计最终体现为源代码。从根本上讲,源代码就是设计。
7.1 设计臭味
如果幸运,你会在项目开始时就想到了系统的清晰图像。系统设计是存在你脑中的一幅至关重要的图像。如果幸运一点,在首次发布时,设计依然保持清楚。
接着,事情开始变糟。软件像一片坏面包一样开始腐化。随着时间流逝,腐化蔓延、增长。丑陋腐烂的痛处和疖子在代码中积累,使它变得越来越难以维护。最后,仅仅进行最简单的更改,也需要花费巨大的努力,以至于开发人员和一线管理人员强烈要求重新设计。
这样的重新设计很少会成功。虽然设计人员开始时的意图是好的,但是他们发现自己正朝一个移动目标设计。老系统不断发展变化,而新的设计必须跟得上这些变化。这样,甚至在第一次发布前,新的设计中就积累了很多瑕疵和弊病。
7.1.1 设计臭味——腐化软件的气味
当软件出现下面任何一种气味时,就表明软件正在腐化。
- 僵化性
- 脆弱性
- 顽固性
- 粘滞性
- 不必要的复杂性
- 不必要的重复
- 晦涩性
7.1.2 僵化性
僵化性是指难以对软件进行改动,即使是简单的改动。如果单一的改动会导致有依赖关系的模块中的连锁改动,那么设计就是僵化的。必须要改动的模块越多,设计就越僵化。
大部分开发人员都会以这样或那样的方式遇到过这种情况。他们会被要求做一个看起来简单的改动。他们仔细检查这个改动并对所需的工作做出了一个估算。但是过了一会儿,当他们实际进行改动时,会发现许多改动带来的影响自己并没有预测到。他们发现自己要在庞大的代码中搜寻这个变动,要改的模块数要远远超出最初估算,并且不断发现其他一些必须要记得做的更改。最后,改动所花费的时间比初始估算要长。当问他们为何估算得如此不准确时,他们会重复开发人员惯用的悲叹,“它比我想象的要复杂得多!”
7.1.3 脆弱性
脆弱性是指,在进行一个改动时,可能会导致程序的许多地方出现问题。常常是,出现新问题的地方与改动的地方并没有概念上的关联。要修正这些问题又会引出更多的问题,从而开发团队就像一只不停追逐自己尾巴的狗一样忙得团团转。
随着模块脆弱性增加,改动会引出意想不到的问题的可能性就越来越大。这看起来很荒谬,但是这样的模块非常常见。这些模块需要不断地修补——它们从来不会从错误列表中去掉。开发人员知道需要对它进行重新设计,但是谁都不愿意去面对重新设计中的难以琢磨性,你越是修改它们,它们举变得越糟。
7.1.4 顽固性
顽固性是指,设计中包含了对其他系统有用的部分,但是要把这些部分从系统中分离出来所需的努力和风险却是巨大的。这是一种令人遗憾,但非常常见的情形。
7.1.5 粘滞性
粘滞性有两种形式:软件的粘滞性和环境的粘滞性。当面临一个改动时,开发人员常常会有多种改动方法。其中,一些方法会保持设计;而另一些会破坏设计(也就是拼凑的方法)。当可以保持系统设计的方法比拼凑手法更难应用时,就表明设计具有高的粘滞性。
当开发环境迟钝、抵消时,就会产生环境的粘滞性。例如,如果编译所花费的时间很长,那么开发人员就会被引诱去做不会导致大规模编译的改动,即使那些改动不再保持设计。如果源代码控制系统要几个小时去签入仅仅几个文件,那么开发人员就会被引诱去做那些尽可能少签入的改动,而不管改动是否保持设计。
无论项目具有哪种粘滞性,都很难保持项目中的软件设计。我们希望创建易于保持和改进设计的系统以及项目环境。
7.1.6 不必要的复杂性
如果设计中包含了当前没有用的组件部分,它就含有不必要的复杂性。当开发人员预测需求的变化,并在软件中放置了处理潜在变化的代码时,常常会出现这种情况。起初,这样看起来像是一件好事。毕竟,为将来的变化做准备会保持代码的灵活性,并且可以避免以后再进行痛苦的改动。
糟糕的是,结果常常正好相反。为过多的可能性做准备,致使设计中含有绝不会用到的结构,从而变得混乱。一些准备也许会带来回报,但是更多的不会。同时,设计背负着这些不会用到的部分,使软件变得复杂,并且难以理解。
7.1.7 不必要的重复
复制和粘贴是灾难性的代码编辑操作。时常会看到一些构建于许多重复代码片段之上的软件系统。同样的代码以稍微不同的形式一再出现时,就表示开发人员忽略了抽象。对于他们来说,发现所有的重复并通过适当的抽象去消除它们的做法可能没有高的优先级别,但是这样做非常有助于使系统更加易于理解和维护。
当系统中有重复的代码时,对系统进行改动会变得困难。在一个重复的代码体系中发现错误必须要在每个重复体中一一修正。不过,由于每个重复体之间都有细微的差别,所以修正的方式也不总是相同的。
7.1.8 晦涩性
晦涩性是指模块难以理解。代码可以用清晰、富有表达力的方式编写,也可以用晦涩、费解的方式编写。代码随着时间的演化,往往会变得越来越晦涩。为了使代码的晦涩性保持最低,就需要保持地保持代码清新和富有表达力。
当开发人员最初编写一个模块是,代码对于他们来说看起来也许是清晰的。毕竟,他们专注于代码的编写,并且熟悉代码的细节。在对代码的熟悉程度减退以后,他们或许会回过头来再去看那个模块,并想知道他们会怎么编写如此糟糕的代码。为了防止这种情况的发生,开发人员必须要站在代码阅读者的位置,努力对它们的代码进行重构,这样代码阅读者就可以理解代码。他们的代码也需要被其他人评审。
7.2 软件为何会腐化
在非敏捷环境中,由于需求没有按照初始设计预期的方式进行变化,从而导致了设计的退化。通常,改动都很急迫,并且进行改动的开发人员对于原始设计思路并不熟悉。因而,虽然可以对设计进行改动,但是却在某种程度上违反了原始设计。随着改动的不断进行,这些违反渐渐地积累,直至恶性肿瘤出现。
然而,我们不能因为设计退化而去责怪需求的变化。作为软件开发人员,我们非常了解需求会变化。事实上,我们中的大多数人都认识到需求是项目中最不稳定的要素。如果我们的设计由于持续、大量的需求变化而失败,那就表明我们的设计和实践本身是有缺陷的。我们必须要设法找到一种方法,使得设计对于这种变化具有弹性,并且应用一些实践来防止腐化。
敏捷团队依靠变化来获取活力。团队几乎不进行预先设计,因此,不需要一个成熟的初始设计。他们更愿意保持系统尽可能的干净简单,并且用许多单元测试和验收测试作为支援。这保持了设计的灵活性、易更改性。团队利用这种灵活性,持续地改进设计,以便于每次迭代结束所产生的系统都具有最适合于那次迭代中需求的设计。
7.3 Copy程序(一个输入输出程序的例子)
7.3.1 熟悉的场景
需求在变化
得寸进尺
期望变化
我们生活在一个需求不断变化的世界中,我们的工作是要保证我们的软件能够经受得住那些变化。
7.3.2 Copy程序(一个输入输出程序的例子)的敏捷设计
在要实现新需求时,团队抓住这次机会去进行设计,以便设计对于将来同类变化具有弹性,而不是设法去给设计打补丁。
团队遵循了开放-封闭原则(Open-Closed Principle,OCP)。这个原则指导我们设计出无需修改即可扩展的模块。
团队不是一开始设计时就试图预测程序将如何变化。团队以最简单的方法编写模块。仅当需求最终却是变化时,团队才修改模块的设计,使之对该种变化具有弹性。
7.4 结论
敏捷设计是一个过程,不是一个事件。它是一个持续的应用原则、模式以及实践来改进软件的结构和可持续性的过程。它致力于保持系统设计在任何时间都尽可能的简单、干净以及富有表达力。
摘自:《敏捷软件开发:原则、模式与实践(C#版)》Robert C.Martin Micah Martin 著
来源:oschina
链接:https://my.oschina.net/u/4407434/blog/4043236