博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

敏捷思维: 架构设计中的方法学(5)--简单设计

Posted on 2011-06-06 20:18  李大嘴  阅读(206)  评论(0编辑  收藏  举报

简介: XP非常强调简单的设计原则:能够用数组实现的功能决不用链表。在其它Agile方法中,简单的原则也被反复的强调。在这一章,我们就对简单性做一个全面的了解。

Context

架构应该设计到什么程度?


Problem

软件的架构都是非常的复杂的,带有大量的文档和图表。开发人员花在理解架构本身上的时间甚至超出了实现架构的时间。在前面的文章中,我们提到了一些 反对象牙塔式架构的一个原因,而其中的一个原因就是象牙塔式架构的设计者往往在设计时参杂进过多的自身经验,而不是严格的按照需求来进行设计。

在软件开发领域,最为常见的设计就是"Code and Fix"方式的设计,设计随着软件开发过程而增长。或者,我们可以认为这种方式根本就不能算是设计,它抱着一种船到桥头自然直的态度,可是在设计不断改动 之后,代码变得臃肿且难以理解,到处充满着重复的代码。这样的情形下,架构的设计也就无从谈起,软件就像是在风雨中的破屋,濒临倒塌。

针对于这种情形,新的设计方式又出现了,Martin Fowler称这种方式为"Planned Design"。和建筑的设计类似,它强调在编码之前进行严格的设计。这也就是我们在团队设计中谈到的架构设计师的典型做法。设计师们通常不会去编程,理由是在土木工程中,你不可能看到一位设计师还要砌砖头。

"Planned Design"较之"Code and Fix"进步了许多,但是还是会存在很多问题。除了在团队设计中我们谈的问题之外,需求变更将会导致更大的麻烦。因此,我们理所当然的想到进行"弹性设计":弹性的设计能够满足需求的变更。而弹性的设计所付出的代价就是复杂的设计。

题外话:

这里我们谈论"Planned Design"引出的一些问题,并没有任何排斥这种方式的意思。"Planned Design"还是有很多可取之处的,但也有很多需要改进的地方。事实上,本文中我们讨论的架构设计方式,本质上也是属于"Planned Design"方式。和"Planned Design"相对应的方式是XP所主张的"Evolutionary Design"方式,但是这种方式还有待于实践的检验,并不能简单的说他就一定要比"Planned Design"先进或落后。但可以肯定的一点是:"Evolutionary Design"方式中有很多的思想和技巧是值得"Planned Design"借鉴的。


Solution

XP中有两个非常响亮的口号:"Do The Simplest Thing that Could Possibly Work"和"You Aren't Going to Need It"(通常称之为YAGNI)。他们的核心思想就是不要为了考虑将来,把目前并不需要的功能加到软件中来。

粗看之下,会有很多开发人员认为这是不切实际的口号。我能理解这种想法,其实,在我热衷于模式、可重用组件技术的时候,我对XP提倡的简单的口号嗤之以鼻。但在实际中,我的一些软件因为复杂设计导致开发成本上升的时候,我重新思考这个问题,发现简单的设计是有道理的。

降低开发的成本

不论是模式,可重用组件,或是框架技术,目的都是为了降低开发的成本。但是他们的方式是先进行大量的投入,然后再节省后续的开发成本。因此,架构设计方面的很多思路都是围绕着这种想法展开的,这可能也是导致开发人员普遍认为架构设计高不可攀的原因。

XP的方式恰恰相反,在处理第一个问题的时候,不必要也不可能就设计出具有弹性、近乎完美的架构来。这项工作应该是随着开发的演进,慢慢成熟起来的。我不敢说这种方式肯定正确,但是如果我们把生物的结构视同为架构,这种方式不是很类似于自然界中生物的进化方式吗?

在一开始就制作出完美的架构的设想并没有错,关键是很难做到这一点。总是会有很多的问题是你在做设计时没有考虑到的。这样,当一开始花费大量精力设 计出的"完美无缺"的架构必然会遇到意想不到的问题,这时候,复杂的架构反而会影响到设计的改进,导致开发成本的上升。这就好比如果方向错了,交通工具再 快,反而导致错误的快速扩大。Martin Fowler在他的论文中说,"Working on the wrong solution early is even more wasteful than working on the right solution early"(提前做一件错事要比提前做一件对的事更浪费时间),相信也是这个道理。

更有意思的是,通常我们更有可能做错。在我们进行架构设计的时候,我们不可能完全取得详细的需求。事实上,就算你已经取得了完整的需求,也有可能发 生变化。这种情况下做出的架构设计是不可能不出错的。这样,浪费大量的时间在初始阶段设计不可能达到的"完美架构",倒不如把时间花在后续的改进上。

提升沟通的效率

我们在团队设计中已经谈过了团队设计的目标之一就是为了降低沟通的成本,以期让所有人都能够理解架构。但是如果架构如果过于复杂,将会重新导致沟通成本的上升,而且,这个成本并不会随着项目进行而降低,反而会因为上面我们提到的遇到新的问题导致沟通成本的持续上升。

简单的架构设计可以加快开发团队理解架构的速度。我们可以通过两种方式来理解简单的含义。首先,简单意味着问题的解不会非常的复杂,架构是解决需求 的关键,无论需求再怎么复杂多变,总是可以找出简单稳定的部分,我们可以把这个简单稳定的部分做为基础,再根据需要进行改进扩展,以解决复杂的问题。在示 例中,我们提到了measurement pattern,它就是按照这种想法来进行设计的。

其次,简单性还体现在表示的简单上。一份5页的文档就能够表达清楚的架构设计为什么要花费50页呢?同样的道理,能够用一副简单的图形就能够表示的 架构设计也没有必要使用文档。毕竟,面对面的沟通才是最有效率的沟通,文档不论如何的复杂,都不能被完全理解,而且,复杂的文档,维护起来也需要花费大量 的时间。只有在两种情况下,我们提倡使用复杂的文档:一是开发团队没有办法做到面对面沟通;二是开发成果要作为团队的知识积累起来,为下一次开发所用。

考虑未来

我们之所以考虑未来,主要的原因就是需求的不稳定。因此,我们如果考虑未来可能发生的需求变化,就会不知觉的在架构设计中增加复杂的成分。这违背的简单的精神。但是,如果你不考虑可能出现的情况,那些和目前设计格格不入的改变,将会导致大量的返工。

还记得YAGNI吗?原则上,我们仍然坚持不要在现有的系统中为将来可能的情况进行设计。但是,我们必须思考,必须要为将来可能出现的情况做一些准备。其实,软件中了不起的接口的思想,不就是源于此吗?因此,思考未来,但等到需要时再实现。

变更案例有助于我们思考未来,变更案例就是你在将来可能要(或可能不要)满足的,但现在不需要满足的需求。当我们在做架构设计的时候,变更案例也将 会成为设计的考虑因素之一,但它不可能成为进行决策的唯一考虑因素。很多的时候,我们沉迷于设计通用系统给我们带来的挑战之中,其实,我们所做的工作对用 户而言是毫无意义的。

架构的稳定

架构简单化和架构的稳定性有什么关系吗?我们说,架构越简单,其稳定性就越好。理由很简单,1个拥有4个方法和3个属性的类,和1个拥有20个方法 和30属性的类相比,哪一个更稳定?当然是前者。而架构最终都是要映射到代码级别上的,因此架构的简单将会带来架构的稳定。尽可能的让你的类小一些,尽可 能的让你的方法短一些,尽可能的让类之间的关系少一些。这并不是我的忠告,很多的设计类的文章都是这么说的。在这个话题上,我们可以进一步的阅读同类的文 章( 关于 refactoring 的思考)。

辨正的简单

因此,对我们来说,简单的意义就是不要把未来的、或不需要实现的功能加入到目前的软件中,相应的架构设计也不需要考虑这些额外的需求,只要刚好能够 满足当前的需求就好了。这就是简单的定义。可是在现实之中,总是有这样或者那样的原因,使得设计趋向复杂。一般来说,如果一个设计对团队而言是有价值的, 那么,付出一定的成本来研究、验证、发展、文档化这个设计是有意义的。反之,如果一个设计没有很大的价值或是发展它的成本超过了其能够提供的价值,那就不 需要去考虑这个设计。

价值对不同的团队来说具有不同的含义。有时候可能是时间,有时候可能是用户价值,有时候可能是为了团队的设计积累和代码重用,有时候是为了获得经 验,有时候是为了研究出可重用的框架(FrameWork)。这些也可以称为目的,因此,你在设计架构时,请注意先确定好你的目的,对实现目的有帮助的事 情才考虑。

Scott W.Ambler在他的文章中提到一个他亲身经历的故事,在软件开发的架构设计过程中,花了很多的时间来设计数据库到业务逻辑的映射架构,虽然这是一件任 何开发人员都乐意专研的事情(因为它很酷)。但他不得不承认,对用户来说,这种设计先进的架构是没有太大的意义的,因为用户并不关心具体的技术。当看到这 个故事的时候,我的触动很大。一个开发人员总是热衷于新奇的技术,但是如果这个新奇技术的成本由用户来承担,是不是合理呢?虽然新技术的采用能够为用户带 来效益,但是没有人计算过效益背后的成本。就我开发过的项目而言,这个成本往往是大于效益的。这个问题可能并没有确定的答案,只能是见仁见智了。


例2. measurement pattern

在分析模式一书中有一个measurement pattern(测量模式),原来它是为了要解决现实中各种各样纷繁复杂的可测量的属性。例如,一个医疗系统中,可能会有身高多高,体重多种,血压多少等上千种可测量的属性。如果分别表示它们,必然导致系统复杂性的上升。因此measurement pattern就从这些属性的可测量的共性出发,研究新的解决方法,提出了measurement pattern的想法:

如图所示,把可测量的属性(Measurement)做为Phenomenon Type的实例,此外,每一个的Person可以拥有多个的Measurement,同时,Measurement还对应处理的属性,例如图中的Quantity,就表示了Measurement的数量和单位。比如,一个人的体重是65公斤,那么,Phenomenon Type就是体重,Quantity的amount是65,units是公斤。


图 5.牋 measurement pattern 的类图
 

这其实是一个很简单的设计,但它清楚的表示了属性之间的关系,简化了数千种的属性带来的复杂性。此外,我们进一步思考,就会发现,这种架构只是针对 目前出现属性众多的问题的基本解决方法,它还可以根据具体的需要进行扩展,例如,实现动态添加单位,或实现不同单位的转化等问题。

因此,我们这里展示的其实是一种思考的方法,假想一下,当你在面对一个复杂的医疗系统时,大量的属性和不同的处理方式,你是不是可以从这样复杂的需 求中找出简单的部分来呢?在我们架构设计的第一篇中,我们谈到架构设计的本质在于抽象,这里例子就是最典型的一个例子,在我们传统的想法中,我们都会把身 高、体重等概念做为属性或是类,但是为了满足这里的需求,我们对这些具体的概念做一个抽象,提出可测量类别的概念,并把它设计为类(Phenomenon Type),而把具体的概念做为实例。这种抽象的思想在软件设计中无处不在,例如元类的概念。

更深入的理解

下一章中我们将会讨论迭代设计,其中还会涉及到简单设计的相关知识。建议可以将两章的内容结合起来看。