对话软件大师/马丁·福勒/灵活性与复杂性
目录 |
第四部分:灵活性与复杂性
In this fourth installment, Fowler discusses design decay, flexibility and reusability versus complexity, four criteria for a simple system, and interface design.
在第四部分,Fowler 讨论了设计褪色(design decay)、灵活性和可重用性与复杂性的关系、简单系统的四个条件、以及接口设计(interface design)。
Design Decay and Refactoring 设计褪色与重构
Bill Venners: Why do designs decay over time?
Bill Venners:为什么设计会随着时间而褪色?
Martin Fowler: If you have a notion of planned design, that the design is something that's right at the beginning, then it's kind of inevitable that the design will decay. So many things in life do decay. The point is that you need to know how to keep a design from decaying. That's what refactoring is about, techniques to reverse the decaying process. Refactoring is relatively new technique. We're still not used to what it gives us. But one thing it gives us is you can use refactoring to stop the decaying of designs. In fact, you can reverse the process and actually make designs improve over time.
Martin Fowler:对于计划型设计来说,设计只在最开始时是正确的,随之不可避免地会褪色。生活中的许多事情都会褪色。关键是,你要懂得如何避免设计褪色。而这恰恰是重构的目的——逆转褪色的过程。重构是一种相对来说比较新的技术。我们还未能完全掌握这项技术。不过有一点可以肯定,你可以借助重构来防止设计褪色,甚而逆转褪色过程,使设计随着时间而变得越来越完善。
Flexibility and Complexity 灵活性和复杂性
Bill Venners: In Refactoring you write, "Before I used refactoring I always looked for flexible solutions. Because design changes were expensive, I would look to build a design that would stand up to changes I could forsee. The problem with building a flexible design is that flexibility costs." What is the cost and what is the alternative?
Bill Venners:在《重构》一书中你写道:“在学会重构之前,我总是力图找到灵活的方案。因为设计变动的代价非常高,因而我希望我的设计能够胜任我所能预知的变化。但问题是,灵活性是有代价的。”那么,灵活性的代价是什么?有什么解决之道么?
Martin Fowler: The cost of flexibility is complexity. Every time you put extra stuff into your code to make it more flexible, you are usually adding more complexity. If your guess about the flexibility needs of your software is correct, then you are ahead of the game. You've gained. But if you get it wrong, you've only added complexity that makes it more difficult to change your software. You're obviously not getting the payback.
Martin Fowler:灵活性的代价就是复杂性。每次当你往代码中加入一些额外的东西以提高灵活性时,通常也使你的代码变得更加复杂。假如你的预期是对的,未来确实需要这种灵活性,那么你的超前工作得到了回报。但如果你的预期是错的,那么你所引入的复杂性将使软件变得更加难以改动,因而该灵活性是毫无意义的。
It's not hard to guess wrong about flexibility needs. You can guess wrong if requirements change. What you think is a requirement for flexibility now may go away or change in the future. You can also guess wrong if you put extra code into the program to improve flexibility but you don't get it quite right. You get more complexity without getting the flexibility you were after.
而这种预期是很容易出错的。比如,当需求发生变化时,你所以为的对灵活性的需求可能随之变化甚至有可能不复存在。再比如,你添加了一些额外的代码,指望它们能提高灵活性,但这些代码本身就有问题。结果是既增加了复杂性,又未能实现灵活性,真是“赔了夫人又折兵”。
The alternative is to use the XP approach and not put the flexibility in at all. XP says, since most of the time we get it wrong, just don't put the flexibility in there. Now if you can't evolve your design safely, then that is a foolish route to take. But if you can evolve your design safely, it becomes quite a nice approach. In fact it becomes a self-reinforcing approach. If you strive to keep your design as simple as possible by avoiding speculative flexibility, then it's easier to change the code because you have less complication to deal with. The code is easier to understand and easier to change. As a result, you can make changes much more quickly.
而解决之道就是极限编程。事实上,你根本就不需要考虑灵活性。极限编程理论认为,既然我们的预期在大多数情况下都是错的,那么就把灵活性放在一边好了。那种冒进式的提升设计的办法是拔苗助长;平稳地改进设计才是可取之道。事实上,设计的改进是一个自我强化(self-reinforcing)的过程。如果你能够使设计尽可能简洁,避免那些无谓的灵活性,那么你所要面对的复杂性就会小很多,也就越容易对代码做出改动。代码会更容易被读懂和被改动,你也能够更快地对软件做出调整。
Flexible versus Reusable 灵活性和可重用性
Bill Venners: What about reuse? Is flexible just another word for reusable?
Bill Venners:那怎么看“可重用”呢?“灵活”是不是“可重用”的另一个代名词?
Martin Fowler: No, but the problem is that in order to get code that's reusable you need to make it very flexible. And a lot of the time your reuse benefit doesn't pay off, either because you end up never needing to reuse it, or because the anticipated modes of reuse weren't what you thought they were. Often the flexibility you put in isn't the flexibility you ultimately need.
Martin Fowler:不,不是的。但问题是,为了使代码可重用,你必须使它灵活。很多时候,可重用性给你带来不了什么好处,或者是因为你根本就用不着它,或者是因为你所期待的可重用性与实际情况风马牛不相及。你在代码中所加入的灵活性往往并不是你最终所需要的灵活性。
Bill Venners: Are there times when it makes sense to make something more reusable? How would I decide?
Bill Venners:那么,是否有些时候需要使事情变得更可重用一些?我该怎么判断呢?
Martin Fowler: My attitude with reuse is that reuse is something you evolve. You build an application to solve the problems of the application. If you build another similar application, then you begin to factor out some common pieces. If you build a third similar application, you factor out more common pieces. Then you'll begin to have something like a reusable framework. My definite recommendation is don't attempt to define a reusable framework first and then build applications on top of it. Rather, evolve the framework while building the applications.
Martin Fowler:我认为可重用也是一个逐步实现和完善的过程。起先,为了解决某些实际问题,你开发了一个应用。接下来,你可能开发了另一个类似的应用。这时,你就可以从两个应用中提取出一些公共的片段。假如你又开发了第三个类似的应用,那么你可能提取出更多的公共片段。在此基础上,你可能就会得到一个类似“可重用的框架”的东西。我强烈建议,不要试图一开始就定义一个可重用的框架然后在此基础上开发应用,相反,应该是在开发过程的过程中,逐渐形成和完善框架。
Up-Front Design versus Refactoring 预先设计与重构
Bill Venners: How would you describe the balance between up front design and refactoring?
Bill Venners:你认为预先设计与重构应该各占多大比重?
Martin Fowler: Before I really came across refactoring, particularly in conjunction with automated testing, I tended to look at design as something I have to get right at the beginning. Now, I look at design as something I can often do a fairly small amount of up front. I let most of the design flow from the evolutionary process. So I feel that there's been a shift in balance. Before, I might have preferred—and these percentages are purely illustrative—80% of my design in planned mode and 20% of it as the project went on. Now I'd perhaps reverse those percentages.
Martin Fowler:在遇到重构之前,特别是在把重构与自动测试(automated testing)结合使用之前,我习惯于把设计看作是在开始阶段必须做好的一件事情。而现在,我认为不需要做很多的预先设计,大部分设计都是在某种进化过程中完成的。预先设计逐渐让位于重构。比如说,在以前,我可能会倾向于80%的设计都是预先做好的,20%是在项目进程中完善的。而现在,这个比例可能要倒过来。
Bill Venners: Much more evolutionary than up-front now.
Bill Venners:也就是说,进化型设计的比重要比预先设计大得多。
Martin Fowler: Much more evolutionary design. Much less up-front.
Martin Fowler:没错,更多的进化型设计,更少的预先设计。
Criteria for a Simple System 简单系统的条件
Bill Venners: In Refactoring, you quote Kent Beck's four criteria for a simple system: 1) Runs all the tests. 2) Reveals all the intention. 3) No duplication. 4) Fewest number of classes or methods. What is simplicity to you?
Bill Venners:在《重构》一书中,你引用了 Kent Beck 给出的简单系统的四个条件:1)通过所有的测试;2)揭示所有的意图;3)没有重复代码;4)使用最少的类和方法。那么,对你来说,简单性是什么?
Martin Fowler: I think it is difficult to come up with a definition of simplicity. I quite like Kent's four rules. The first one reminds us that we have to run all the tests.
Martin Fowler:我想,很难给简单性一个定义。我很喜欢 Kent 的四个条件。第一条提醒我们必须要通过所有的测试。
Bill Venners: But what does that have to do with simplicity? I could have something very complicated that runs all its tests.
Bill Venners:但是,这与简单性有什么关系呢?我完全可以把事情搞得很复杂,也一样通过所有的测试。
Martin Fowler: Yes, but to have a well designed system it's got to work. If you remove the constraint that my system actually got to work...
Martin Fowler:这倒是没错。但是,一个设计良好的系统,首先必须是一个能运行的系统。如果你把这个约束去了,那……
Bill Venners: Oh, you're saying running the tests is necessary but not sufficient.
Bill Venners:噢,你是说,通过测试是必要条件但非充分条件。
Martin Fowler: Exactly. Once the tests run you ask yourself, did you remove all the duplication? The third criteria, reveals all the intention, is the really hard part, because it is very subjective. You should be able to read the code and see what it does. The design intent of the code should just be apparent as you look at the code. A simple example is a well-named method. Rather than naming a method x74-3, and then providing a comment that says what the method does, why don't you just say what the method does in the name? That's more intention revealing. Ask yourself, does the structure of your code, the way the code is named, etc., really reveal the intention of your code?
Martin Fowler:没错。一旦测试通过,你接着就该问自己,是否所有的重复代码都被清除干净了?而另一个条件,揭示所有的意图,则是非常难把握的一个条件,因为它太主观了。基本上说,你应该能够读懂代码,知道它在干什么。代码所包含的设计意图应该能够通过代码很明显地体现出来。像是命名合理的方法就是一个简单的示例。与其把一个方法起名为 x74-3,然后通过注释来说明这个方法是干什么的,不如通过该方法的名字直接告诉人们它要做什么。这样更能起到揭示其意图的作用。问一下你自己,你的代码结构、命名方式等等,是否揭示了代码的意图?
Once all of that is out of the way, you obviously don't want more lines of code than you need. Only at the end do you minimize the number of classes and methods—only once all the other criteria are true. It is more debatable which order to apply the second and third criteria, reveals all the intention and has no duplication. But running the tests has to be the number one thing, and the minimum amount of code criteria should be the last thing.
当上面提到的这些问题都解决了的时候,你显然还希望代码不要有多余的部分。注意,最小化所使用的类和方法一定是最后一步——在其他条件都满足之后。人们常常对于第二个和第三个条件的次序有不同看法。但通过所有的测试一定是第一位的,而最小化代码量则一定是最后一步。
Designing Interfaces 设计接口
Bill Venners: The way I think about design, I tend to think a lot in terms of interfaces. The literature on patterns and agile methods, Design Patterns (the Gang of Four book), Kent Beck's Extreme Programming Explained, your Refactoring book, talk a lot about code, which is both interfaces and implementations. What is the design role of thinking in terms of interfaces?
Bill Venners:当我想到设计的时候,总是习惯性地从接口这个角度去考虑。而在有关模式和敏捷方法的一些书籍中,包括“四人帮”的《设计模式》(Design Patterns),Kent Beck 的《解析极限编程》(Extreme Programming Explained),还有你的《重构》,往往都是把接口和实现作为一个整体的代码来讨论。那么,在设计中是否还有必要从接口这个角度去考虑?
Martin Fowler: I think thinking in terms of interfaces is very fundamental. My take on the Gang of Four book is that it said more about the role of interfaces than almost any other book on object-oriented design.
Martin Fowler:我想,从接口这个角度去考虑是非常基本的。事实上,我认为“四人帮”的那本书比其它任何一本关于面向对象设计的书都更强调接口的作用。
Bill Venners: How's that?
Bill Venners:为什么这么说?
Martin Fowler: Because every other book on object-oriented design might say somewhere in the book, "Oh yeah, interfaces are important." But I think the Gang of Four book put a very strong piece in the early chapters about what it means to program to an interface. And then almost every pattern in that book is about how interfaces can vary independently to implementations and the benefits of programming to an interface.
Martin Fowler:其它关于面向对象设计的书也许会在某处这样说道:“啊,接口实在是太重要了。”而“四人帮”的那本书,在前几章就开宗明义地指出,面向接口编程(program to an interface)到底意味着什么。而这本书后面的几乎每一个模式,都在说明接口是如何独立于实现而变化的,以及面向接口编程的好处。
I think that's also true of extreme programming. The whole value of test-first design (test-driven development as Kent is calling it), in fact the primary driving reason of it, is because when you are writing the tests first you are thinking about the interface. Writing a test is designing the interface. When you write the test first, then implement the code, you are figuring out the interface first, then implementing the interface. First figure out the interface, then implement it. And then you have a whole bunch of automated tests that describe how the interface works.
我想,极限编程也是一样。测试优先设计(test-first design)—— Kent 称之为测试驱动开发(test-driven development)——的全部意义,或者说,最主要的驱动因素,就是接口。因为当你先编写测试的时候,你实际上所考虑的是接口。编写测试就是在设计接口。当你先编写测试后实现代码的时候,你实际上是先定义清楚了接口,然后再实现接口。接下来你就可以编写一整套自动测试来描述接口是如何工作的。
Bill Venners: I hadn't gotten that from my reading of the literature. The way I kind of approach it is, I design the interface first, then sometimes I write the test first, sometimes write the test second, sometimes don't write the test.
Bill Venners:我在读这些文献的时候未曾体会到这一层。我所领会的是,首先设计接口,接下来有时候会先写测试,有时候会后写测试,有时候则不写测试。
Martin Fowler: Not a problem.
Martin Fowler:没关系的。
Bill Venners: If writing the test first kind of correlates to thinking about designing the interface, then it is kind of the same thing.
Bill Venners:既然先写测试与考虑如何设计接口是相关的,那么它们是否是一回事?
Martin Fowler: Oh yeah. But I think writing the test first speaks a lot more to many developers. It is hard to think about interfaces, but when you sit there and you say, I've got to come up with a test that's going to make this tiny little piece of functionality work. It will have you thinking, what's the nicest way to write this test? And you're thinking about the interface. It is not stated explicitly, but that's what you're doing. You're thinking about the interface. You're also doing it incrementally. You're not saying, here I've got to do this class, let's come up with the interface of this entire class and then implement it. You're saying, this class needs to have a very small piece of responsibility. Write the test for it. Come up with the interface for that as you're writing the test.
Martin Fowler:可以这么说。不过我想,对许多程序员来说,先写测试的说法会更具体形象些。构思接口是件困难的事情。但是,当你坐在那里对自己说“我需要写一个测试,以使这一个小功能得以实现”的时候,你就要考虑,怎样写这个测试最好?而这时,你就在考虑接口。这是一个潜移默化的过程——你的的确确是在构思接口,而且是以一种渐进的方式。你不会对自己说,“啊,我需要构造这个类,让我们来把这个类的所有接口都搞清楚,然后再实现之吧!”相反,你会说,“嗯,这个类需要实现这么一小块功能。来为此写个测试吧!”在编写测试的时候,接口就随之浮现出来。
