Bill Venners与Martin Fowler的对话(灵活性与复杂性)

对话马丁·福勒(Martin Fowler)——第四部分:灵活性与复杂性
简介

在第四部分,Fowler 讨论了设计褪色(design decay)、灵活性和可重用性与复杂性的关系、简单系统的四个条件、以及接口设计(interface design)。


设计褪色与重构

Bill Venners:为什么设计会随着时间而褪色?
Martin Fowler:对于计划型设计来说,设计只在最开始时是正确的,随之不可避免地会褪色。生活中的许多事情都 会褪色。关键是,你要懂得如何避免设计褪色。而这恰恰是重构的目的——逆转褪色的过程。重构是一种相对来说比较新的技术。我们还未能完全掌握这项技术。不 过有一点可以肯定,你可以借助重构来防止设计褪色,甚而逆转褪色过程,使设计随着时间而变得越来越完善。


灵活性和复杂性

Bill Venners:在《重构》一书中你写道:“在学会重构之前,我总是力图找到灵活的方案。因为设计变动的代价非常高,因而我希望我的设计能够胜任我所能预知的变化。但问题是,灵活性是有代价的。”那么,灵活性的代价是什么?有什么解决之道么?
Martin Fowler:灵活性的代价就是复杂性。每次当你往代码中加入一些额外的东西以提高灵活性时,通常也使你的 代码变得更加复杂。假如你的预期是对的,未来确实需要这种灵活性,那么你的超前工作得到了回报。但如果你的预期是错的,那么你所引入的复杂性将使软件变得 更加难以改动,因而该灵活性是毫无意义的。
而这种预期是很容易出错的。比如,当需求发生变化时,你所以为的对灵活性的需求可能随之变化甚至有可能不复存在。再比如,你添加了一些额外的代码,指望它们能提高灵活性,但这些代码本身就有问题。结果是既增加了复杂性,又未能实现灵活性,真是“赔了夫人又折兵”。
而解决之道就是极限编程。事实上,你根本就不需要考虑灵活性。极限编程理论认为,既然我们的预期在大多数情况下都是错的,那么就把灵活性 放在一边好了。那种冒进式的提升设计的办法是拔苗助长;平稳地改进设计才是可取之道。事实上,设计的改进是一个自我强化(self- reinforcing)的过程。如果你能够使设计尽可能简洁,避免那些无谓的灵活性,那么你所要面对的复杂性就会小很多,也就越容易对代码做出改动。代 码会更容易被读懂和被改动,你也能够更快地对软件做出调整。


灵活性和可重用性

Bill Venners:那怎么看“可重用”呢?“灵活”是不是“可重用”的另一个代名词?
Martin Fowler:不,不是的。但问题是,为了使代码可重用,你必须使它灵活。很多时候,可重用性给你带来不了什么好处,或者是因为你根本就用不着它,或者是因为你所期待的可重用性与实际情况风马牛不相及。你在代码中所加入的灵活性往往并不是你最终所需要的灵活性。
Bill Venners:那么,是否有些时候需要使事情变得更可重用一些?我该怎么判断呢?
Martin Fowler:我认为可重用也是一个逐步实现和完善的过程。起先,为了解决某些实际问题,你开发了一个应 用。接下来,你可能开发了另一个类似的应用。这时,你就可以从两个应用中提取出一些公共的片段。假如你又开发了第三个类似的应用,那么你可能提取出更多的 公共片段。在此基础上,你可能就会得到一个类似“可重用的框架”的东西。我强烈建议,不要试图一开始就定义一个可重用的框架然后在此基础上开发应用,相 反,应该是在开发过程的过程中,逐渐形成和完善框架。


预先设计与重构

Bill Venners:你认为预先设计与重构应该各占多大比重?
Martin Fowler:在遇到重构之前,特别是在把重构与自动测试(automated testing)结合使用之前,我习惯于把设计看作是在开始阶段必须做好的一件事情。而现在,我认为不需要做很多的预先设计,大部分设计都是在某种进化过 程中完成的。预先设计逐渐让位于重构。比如说,在以前,我可能会倾向于80%的设计都是预先做好的,20%是在项目进程中完善的。而现在,这个比例可能要 倒过来。
Bill Venners:也就是说,进化型设计的比重要比预先设计大得多。
Martin Fowler:没错,更多的进化型设计,更少的预先设计。


简单系统的条件

Bill Venners:在《重构》一书中,你引用了 Kent Beck 给出的简单系统的四个条件:1)通过所有的测试;2)揭示所有的意图;3)没有重复代码;4)使用最少的类和方法。那么,对你来说,简单性是什么?
Martin Fowler:我想,很难给简单性一个定义。我很喜欢 Kent 的四个条件。第一条提醒我们必须要通过所有的测试。
Bill Venners:但是,这与简单性有什么关系呢?我完全可以把事情搞得很复杂,也一样通过所有的测试。
Martin Fowler:这倒是没错。但是,一个设计良好的系统,首先必须是一个能运行的系统。如果你把这个约束去了,那……
Bill Venners:噢,你是说,通过测试是必要条件但非充分条件。
Martin Fowler:没错。一旦测试通过,你接着就该问自己,是否所有的重复代码都被清除干净了?而另一个条件, 揭示所有的意图,则是非常难把握的一个条件,因为它太主观了。基本上说,你应该能够读懂代码,知道它在干什么。代码所包含的设计意图应该能够通过代码很明 显地体现出来。像是命名合理的方法就是一个简单的示例。与其把一个方法起名为 x74-3,然后通过注释来说明这个方法是干什么的,不如通过该方法的名字直接告诉人们它要做什么。这样更能起到揭示其意图的作用。问一下你自己,你的代 码结构、命名方式等等,是否揭示了代码的意图?
当上面提到的这些问题都解决了的时候,你显然还希望代码不要有多余的部分。注意,最小化所使用的类和方法一定是最后一步——在其他条件都满足之后。人们常常对于第二个和第三个条件的次序有不同看法。但通过所有的测试一定是第一位的,而最小化代码量则一定是最后一步。


设计接口

Bill Venners:当我想到设计的时候,总是习惯性地从接口这个角度去考虑。而在有关模式和敏捷方法的一些书籍 中,包括“四人帮”的《设计模式》(Design Patterns),Kent Beck 的《解析极限编程》(Extreme Programming Explained),还有你的《重构》,往往都是把接口和实现作为一个整体的代码来讨论。那么,在设计中是否还有必要从接口这个角度去考虑?
Martin Fowler:我想,从接口这个角度去考虑是非常基本的。事实上,我认为“四人帮”的那本书比其它任何一本关于面向对象设计的书都更强调接口的作用。
Bill Venners:为什么这么说?
Martin Fowler:其它关于面向对象设计的书也许会在某处这样说道:“啊,接口实在是太重要了。”而“四人帮” 的那本书,在前几章就开宗明义地指出,面向接口编程(program to an interface)到底意味着什么。而这本书后面的几乎每一个模式,都在说明接口是如何独立于实现而变化的,以及面向接口编程的好处。
我想,极限编程也是一样。测试优先设计(test-first design)—— Kent 称之为测试驱动开发(test-driven development)——的全部意义,或者说,最主要的驱动因素,就是接口。因为当你先编写测试的时候,你实际上所考虑的是接口。编写测试就是在设计 接口。当你先编写测试后实现代码的时候,你实际上是先定义清楚了接口,然后再实现接口。接下来你就可以编写一整套自动测试来描述接口是如何工作的。
Bill Venners:我在读这些文献的时候未曾体会到这一层。我所领会的是,首先设计接口,接下来有时候会先写测试,有时候会后写测试,有时候则不写测试。
Martin Fowler:没关系的。
Bill Venners:既然先写测试与考虑如何设计接口是相关的,那么它们是否是一回事?
Martin Fowler:可以这么说。不过我想,对许多程序员来说,先写测试的说法会更具体形象些。构思接口是件困难 的事情。但是,当你坐在那里对自己说“我需要写一个测试,以使这一个小功能得以实现”的时候,你就要考虑,怎样写这个测试最好?而这时,你就在考虑接口。 这是一个潜移默化的过程——你的的确确是在构思接口,而且是以一种渐进的方式。你不会对自己说,“啊,我需要构造这个类,让我们来把这个类的所有接口都搞 清楚,然后再实现之吧!”相反,你会说,“嗯,这个类需要实现这么一小块功能。来为此写个测试吧!”在编写测试的时候,接口就随之浮现出来。

 

你可能感兴趣的:(设计模式,编程,框架,软件测试,敏捷开发)