Zhangsns

什么是敏捷设计?

     在按照我的理解方式审查了软件开发的生命周期后,我得出一个结论:实际上满足工程设计标准的唯一软件文档,就是源代码清单。“

——Jack Reeves

      1992年,Jack Reeves 在C++ Journal杂志上撰写了一篇题为“什么是软件设计?”的开创性论文。在这篇文章里,Reeves认为软件系统的源代码是它的主要设计文档。用来描绘源代码的图示只是设计的附属物而不是设计本身。结果表明,Jack的论文是敏捷开发的先驱。

      在随后的内容中,我们会经常谈到“设计”。你不应该认为设计就是一组和代码分离的UML图。一组UML图也许描绘了一些部分,但是它不是设计。软件项目的设计是一个抽象的概念。它和程序的概括形状(shape)、结构以及每一个模块、类和方法的详细形状和结构有关。可以使用许多不同的媒介(media)去描绘它,但是它最终体现为源代码。最后,源代码就是设计。

  

1.1 软件出了什么错

      如果幸运,你会在项目开始时就有了想得到的系统的清晰地图像。系统的设计是存在于你头脑中的一副至关重要的图像。如果幸运一点,在首次发布(release)时,设计依然保持清楚。

      接着,事情开始变糟。软件像一片坏面包一样开始腐化。随着时间的流失,腐化蔓延、增长。丑陋腐烂的痛处和疖子在代码中累积,使它变得越来越难以维护。最后,即使仅仅进行最简单的更改,也需要花费巨大的努力,以至于开发人员和一线(front-line)管理人员强烈要求重新设计。

      这样的重新设计很少会成功。虽然设计人员开始时出于好意,但是他们发现自己正朝着一个移动的目标前进。老系统不断地发展、变化,而新的设计必须的跟上这些变化。这样,甚至在第一次发布前,新的设计中就累积了很多的瑕疵和弊病。

1.2 设计的臭味——腐化软件的气味

      当软件出现下面任何一种气味时,就表明软件正在腐化。

·僵化性(Rigidity):很难对系统进行改动,因为每个改动都会迫使许多对系统其它部分的其他改动;

·脆弱性(Fragility):对系统的改动导致系统中和改动的地方在概念上无关的许多地方出现地方出现问题;

·牢固性(Immobility):很难理解开系统的纠结,使之成为一些可在其它系统中重用的组件;

·粘滞性(Viscosity):做正确的事情比做错误的事情要困难;

·不必要的复杂性(Needless Complexity):设计中包含有不具有任何直接好处的基础结构;

·不必要的重复(Needless Repetition):设计中包含有重复的结构,而该重复的结构本可以使用单一的抽象进行统一;

·晦涩性(Opacity):很难阅读、理解。没有很好的表现出意图;

1.2.1:僵化性

     僵化性是只难以对软件进行改动,即使是简单的改动。如果单一的改动会导致有依赖关系的模块中的连锁改动,那么设计就是僵化的。必须要改动的模块越多,设计就越僵化。

     大部分的开发人员都以这样或者那样的方式遇到过这种情况。他们会被要求进行一个开起来简单的改动。他们看了看这个改动并对所需的工作做出了一个合理的估算。但是过了一会儿,当他妈恩实际进行改动时,会发现有许多改动带来的影响并没有预测到。他们发现自己要在庞大的代码中搜寻这个变动,并且要更改的模块数目也远远超出最初估算。最后,改动所花费的时间要远比初始估计长。当问他们为何估算如此不准确时,他们会重复软件开发人员惯用的悲哀,“它比我想象的要复杂的多!”

1.2.2:脆弱性

     脆弱性是指,在进行一个改动时,程序的许多地方就可能出现问题。常常是,出现新问题的地方与改动的地方并没有概念上的关联。要修正这些问题就会引出更多的问题,从而使开发团队就像一只不停追逐自己尾巴的狗一样(忙的团团转)。

     随着模块脆弱性的增加,改动会引出意想不到的问题的可能性就越来越大。着看起来很荒谬,但是这样的模块是非常常见的。这些模块需要不断地修补——他们从来不会被从错误列表中去除,开发人员指导需要对它们进行重新设计(但是谁都不愿意去面对重新设计中的难以琢磨性),你越修正它们,它们就变得越糟。

1.2.3:牢固性

      牢固性是指,设计中包含了对其他系统中有用的部分,但是要把这些部分从系统中分离出来所需要的努力和风险是巨大的。这是一件令人遗憾的事,但却是非常常见的事情。

1.2.4:粘滞性

      粘滞性有两种表现形式:软件的粘滞性和环境的粘滞性。

      当前面临一个改动时,开发人员常常会发现有多种改动的方法。其中,一些方法会保持设计;而另外一些会破坏设计(也就是生硬的手法)。当那些可以保持系统设计的方法比那些生硬手法更难应用时,就表明设计具有高的粘滞性。做错误的事情是容易的,但是做正确的事情却很难。我们希望在软件设计中,可以容易地进行那些保持设计的变动。

      当开发环境迟钝、低效时,就会产生环境的粘滞性。例如,如果编译所花费的时间很长,那么开发人员就会被引诱去做不会导致大规模重编译的改动,即使那些改动不再保持设计。如果源代码控制系统需要几个小时去check in 仅仅几个文件,那么开发人员就会被引诱去做那些需要尽可能少check in的改动,而不管改动是否会保持设计。

      无论项目具有哪种粘滞性,都很难保持项目中的软件设计。我们希望创建易于保持设计的系统和项目环境。

1.2.5:不必要的复杂性

      如果设计中包含有当前没有用的组成部分,它就含有不必要的复杂性。当开发人员预测需求的变化,并在软件中放置了处理那些潜在变化的代码时,常常会出现这种情况。起初,这样做看起来像是一件好事。毕竟,为将来的变化作准备会保持代码的灵活性,并且可以避免以后再进行痛苦的改动。

      糟糕的是,结构常常正好相反。为过多的可能性做准备,致使设计中含有绝不会用到的结构,从而变得混乱。一些准备也许会带来回报,但是更多的不会。期间,设计背负着这些不会用到的部分,是软件变的复杂,并且难以理解。

1.2.6:不必要的重复

      剪切(cut)和黏贴(paste)也学是有用的文本编辑(text-editing)操作,但是他们却是灾难性的代码编辑(code-editing)操作。时常,软件系统都是构建于众多的重复代码片段之上。例如:

      Ralph 需要编写一些完成某项功能的代码。他浏览了一下他认为可能会完成类似工作的其他代码。他将那块代码拷贝到自己的模块中,并做了适当的修改。

      Ralph 并不知道,他用鼠标获取的代码是由Todd放置在那里的,而Todd是从Lilly编写的模块中获取的。Lilly是第一个完成这项功能的,但是她认识到完成这项功能和完成另一项功能非常类似。她从别处找到了一些完成另外一项功能的代码,剪切、拷贝到她的模块中并做了必要的修改。

      当同样的代码以稍微不同的形式一再出现时,就表示开发人员忽视了抽象。对于他们来说,发现所有的重复并通过适当的抽象去消除他们的做法可能没有高的优先级别,但是这样做非常有助于使系统更加易于理解和维护。

      当系统中有重复的代码时,对系统进行改动会变得困难。在一个重复的代码体中发现的错误必须要在每一个重复体中一一修改。不过,由于每个重复体之间都有细微的差别,所以修正的方式也不总是相同的。

1.2.7:晦涩性

      晦涩性是指模块难以理解。代码可以用清晰、富有表现力的方式编写,或者可以用晦涩、费解的方式编写。代码随着时间而演变,往往会变得越来越晦涩。为了使代码的晦涩性保持最低,就需要持续地保持代码清晰并富有表现力。

      当开发人员最初编写一个模块时,代码对于他们来说看起来也许是清晰的。这是由于他们使自己专注于代码的编写,并且他们对于代码非常的熟悉在熟悉减退之后,他们或许会回过头来再去看哪个模块,并想知道他们怎么会编写如此糟糕的代码。为了防止这种情况发生,开发人员必须要站在代码阅读者的角度,共同努力丢他们的代码进行重构,这样代码的阅读者就可以理解代码。他们的代码也需要被其他人评审。

 

什么激发了软件的腐化

       在非敏捷环境中,由于需求没有按照初始设计遇见的方式进行变化,从而导致了设计的退化。通常,改动都很急迫,并且进行改动的开发人员对于原始的设计思路并不熟悉。因而,虽然对设计的改动可以工作,但是它却以某种方式违反了原始的设计。随着改动的不断进行,这些违反渐渐地积累,设计开始出现臭味。

      然而,我们不能因为设计的退化而责怪需要的变化。作为开发人员,我们对于需求变化有非常好的了解。事实上,我们中的大多数人都认识到需求是项目中最不稳定的要素。如果我们的设计由于持续、大量的需求变化而失败,那就表明我们的设计和实践本身是有缺陷的。我们必须要设法找到一种方法,使得设计对于这种变化具有弹性,并且应用一些实践防止设计腐化。

敏捷团队不允许软件腐化

      敏捷团队依靠变化来获取活力。团队几乎不进行预先(up-front)设计,因此,不需要一个成熟的设计。他们更愿意保持系统设计尽可能的干净、简单,并使用许多单元测试和验收测试作为支援。这保持了设计的灵活性、易于理解性。团队利用这种灵活性,持续地改进设计,以便于每次迭代结束所生成的系统都具有最适合于那次迭代中需求的设计。

 

分类:

技术点:

相关文章: