代码改变世界

简单设计

2011-08-31 08:10  横刀天笑  阅读(5400)  评论(7编辑  收藏

XP(极限编程)里除了大名鼎鼎的TDD,重构等最佳实践外还有一些不怎么被人注意的实践,比如本文想谈的简单设计(Simple Design)。

我们常常说设计要简单,简单即美。甚至敏捷软件开发的四个要素(沟通、反馈、简单、勇气)简单也位于其中。那到底什么是XP开发者眼中所认为的简单呢?

在Kent Beck所著的《解析极限编程》里对简单设计有四条“简单”的描述,通常也被大家称为简单设计四原则:

通过所有测试

体现所有意图

消除重复

类和方法尽量短小

可能和原文有所出入,顺序也不同(注:我这里列出的顺序不代表它的优先级),但要表达的意思是这样的。

通过所有测试

对于通过所有测试这一条,它是与XP的另外一个实践TDD相辅相成的。通过所有测试不仅仅表明程序运行“正确(正确是相对的)”。更重要的是一种设计态度。

要通过测试就得能写测试,要为程序编写测试并不是一件容易的事情,“复杂”或“混乱”的程序是很难测试的。可测试性本来就是设计质量的一项考量标准。铁板一块的程序无法很容易解耦,要对其进行测试只有在铁板的外围进行测试,测试的粒度如此之大,先不说是否好构建测试,即使测试构建好了,如果测试失败了也很难发现问题之所在。

所以光满足可测试性这一条就要死伤很多脑细胞。你得让该模块所依赖的东西能轻松的“注入”进去,这样就能解耦,这样就能单独对这个模块进行测试,这样就能在更小的粒度上进行测试。

比如很简单的一个实例,我们做一个计算税率的程序,假设税率的获取依靠第三方系统提供,那我们在开发之前可以将其划分为几个任务:

  1. 输入金额
  2. 计算(从第三方获取税率)
  3. 输出结果

很好,我们可以这样“即兴设计”出一个方法:

从终端读入金额,然后实例化一个第三方的“本地代理对象”,调用其接口获取税率,然后计算,得到结果后输出到终端。

很简单是不,但是这个方法如何测试呢?你也许可以说:我们可以重定向终端到文件,这样就可以提供测试数据并得到输出,那么第三方系统呢?

我们想想,对于这个小程序,我们最关心的是计算逻辑是否正确,对于从终端读入、从第三方获取税率以及输出到终端都是较难测试的,我们必须把这个依赖解耦,然后就可以对计算逻辑测试了。

解开依赖实际上还不能完全满足可测试性的要求,要这个测试容易构建还得看看你到底有多少依赖。我们常常将一个测试分为三段式:

Given

这里用来构建依赖,以及输入对象

When

这里调用被测试元素

Then

这里用来测试结果

常常有这样的测试,它的Given段非常长,而When和Then非常短小。这就是一种信号:这个玩意儿依赖的东西太多了,需要很多代码来构建依赖,如果有那么两个依赖很难构建那又更困难了。所以说解开依赖还不够,还得要容易构建依赖。实际上依赖太多常常说明一件事情:你的类或方法职责不单一,做的事情太多。这里的类有两个意思:

  1. 要么是你的被测试类的职责不单一,干的事情太多,需要从许多地方获取所需的功能,这样如果有一个依赖改变了,那么你这个类就得变,如果有两个依赖变了,而且变化的方法还不是统一的,那么你这个类就被扯的四分五裂。
  2. 要么是被测试类所依赖的类不单一,本来从一个地方获取功能就可以了,现在要从好几个地方获取。

好了,关于通过所有测试这一条就谈这么多吧,这块的内容要谈可以有很多。

体现所有意图

不管是高德纳所说的文学编程,还是领域驱动开发里面都有体现意图这层意思。体现意图不仅要体现作者(也就是作为开发者的呢)的意图,你当时是怎么想的,还要体现当前业务的意图。我们要将业务需求通过代码来表达出来。其实表达这些意图的方式很简单:起名字。

好的类名,好的方法名,好的变量名

在这些名字上多斟酌一点时间,回报是巨大的。代码阅读的次数会比编写的次数多,所以如果你想节约点阅读的时间,让你的代码生命力更长久点,那么就在这上面多花点时间吧。

消除重复

其实在大部分时候,消除重复往往是我们重构的第一步,也是重构的契机。消除重复往往能诞生出更好的设计结果。因为重复消除了,你总是要使用一些抽象的手段将这些原来使用重复代码,而现在被删除了的地方连接起来。你总有可能提取新的基类或接口。消除重复不仅能降低bug(如果修改了一处重复的代码,你得修改多出,修改的越多就越容易出错),更重要的是能降低复杂性。关于消除重复《Refactor》这本书里讲了很多手段,这里就不啰嗦了。

类和方法尽量短

这里有两个问题:为什么要短?到底要多短?

我总觉得我的脑子太笨,一下子让我理解很多的内容我大脑会混乱。我喜欢的短的类和方法,这样我在同一时刻就可以理解少一点东西。但是也有人说每个类或方法短就会产生很多类和方法。从某种程度上是正确的,但也不对。我们不仅要强调短,我们还得强调集中(或单一职责)。我们不是为了追求短而短,不是说为了短,我把所有的类都拆成两个。短需要短的理由,如果这个方法有500行,但是它的内聚性非常好,干的事儿单一,把它任何一个方法拆出去都显得不完整,那么我觉得没有理由要让它变短。

短的方法和类不仅容易理解,还容易测试,因为短的方法和类“往往”更趋向于职责单一(当然也有例外)。而且短的类和方法又更容易用名字来说明意图了,因为干的事儿少嘛。

那么我到底该多短呢?这个太有争议了。有人说一屏幕能显示就可以了。问为什么一屏显示就可以了啊?答:这样就不用拖动滚动条了。如果你是这个回答我就不认同了。我们要短的类和方法并不是为了不拖动滚动条,如果真的实现了这个目标那也是个副产品。如果你们团队用的是一样的屏幕,你们可以建立一个Guideline说一个类一屏。但理由绝对不是不拖动滚动条。而且注意的是这是一个Guideline,不是死规定。也就是说如果别人有充足的理由,是可以打破这个Guideline的。

PS:我们团队的Guideline是每个方法15行(这只是我们的Guideline,但是我们竭力去维护它)。

我想,如果我们在编写代码是不是那么的随兴发挥,我们字字句句的斟酌一番记住上面简单设计的四个原则,我想离Simple Design不会太远。