refactoring Patterns:第六部分 | ||||
石一楹 (shiyiying@hotmail.com) Refactoring是一种重要的设计辅助工具。特别地,他能够使得传统的up-front设计更简单,也可以改良现有软件的设计。本文阐述了在应用refactoring环境下设计应当具有的特点以及refactoring和OO社团最受人瞩目之一的设计模式之间的关系。 为什么设计应当是简单的? 软件方法学的设计者通常喜欢用建筑打比方。他们说,如果你要建造一座大厦,那么在你画完所有的施工图和建筑规范之前,你从来不应该开始真正施工。 我喜欢这样的比喻,因为建筑学的发展经历了几千年甚至可能是上万年,而软件行业,最多也只有5,60年的时间,我们需要从建筑中学习很多东西。就如模式语言的提出者,著名的建筑学家,Christopher Alexander ,给了设计模式很大的启发。 但是,如果我们只是从建筑学到东西,恐怕还是远远不够。我还不知道哪一栋大厦的外形能够发生变化,或者哪一个大商场在发现自动扶梯位置不太合适时,能够自由移动到一个更加合适的地方(我附近一家大超市的自动扶梯真的让我想到了这一点)。也许要部分达到这样的目的还是有可能的,但肯定要花费极高的代价。但是如果我们去构造一个企业的应用系统,那么我们必须准备好企业发展的同时需要对我们的软件做出改变。 软件的核心就是可变。这种变化不仅仅在于需求的变化,还在于人们理解的变化,而软件这种人活动的产物,必须要随之发生改变。 增量迭代的开发方法学是对软件变化的一种响应。但是正如我们前面所讲,增量迭代模型有可能对系统的设计提出比传统的Waterfall更复杂的要求。你需要预期所有可能的情形,然后才能保证你是可以增量的,也是能够迭代的。 这种两难的境地来源于一个基本的假设: 传统的软件发法学认为,如果我能够尽量早地冻结需求,那么我修改设计的可能性和成本就越小,如果我的设计能够尽早地冻结,那么我修改实现的可能性和成本就越小。要想早早冻结设计,它必须是非常详尽而复杂的。 我把这种思路称为"改变恐惧症",不仅仅因为变化是最难研究的东西(制订出条条框框则简单的多),也因为我们没有足够的经验来适应变化,更重要的是,从来没有哪个时代比今天变化更快。 现在我们从另一个角度来想问题: 这个方法初看起来自相矛盾。但是Kent Beck说: 既然系统在变,那么你不能肯定今天设计的东西一定能够被日后用到。而你需要等待设计完成的成本会很高,因为在你的设计完成之前,没有人可以开始工作。 设计不是独立的。别人要使用你的设计,他必须能够理解你的设计。如果你今天的设计十分复杂,那么你就增加了从今天开始的所有开销,更多的东西需要检查和测试,更多的东西需要理解,更多的东西需要解释。更重要的是,你不能估算出明日的成本。你必须估计日后将要发生什么事情,通常你不可能准确无误地做到这一点。 所以,让设计简单,我们可以通过不断地增量修改设计使得系统更加接近需求,就象我们开车,每次都做一些小小的调整,最后达到目的地。 什么才是简单的设计? 就象我在Duplicate Code里指出的一样,Once and Only Once通常是代码最简单的形式,因为它没有重复,每一个类都有自己简单的责任,每一个方法都有自己简单的意图。 简单的设计最重要的特性就是容易适应变化.为了达到这样的目的,简单设计应当:
如果你看过Kent Beck和其他Agile联盟作者的文章,或者你看过我的Duplicate Code和Long Method,你可能会觉得2和3,4存在着矛盾.因为要达到2的目的,你必须有很多小类和精干的方法,而3,4则要求你具有最少的类和方法.要理解它们之间其实一致的关键在于,你如何决定需要一个新类或者需要一个新的方法.在进行Refactoring时,如果你觉得系统的某一部分具有独立的可对应问题领域的概念时,那么你应当毫不犹豫地使用Extract Class形成一个新类.反之,某一个类无法具有其独立的应用意义,或者该类数据太多而没有行为,这个时候你应当考虑这个类是不是应该被删除.当然,这里面有些例外(如method object)同样道理,如果一个代码片断能够有独立意图的行为,那么不管它的大小,可能是一个简单的表达式,都应该有独立的方法,但如果没有这样清晰的意图,再多的代码都可以在一个方法里面. 显然,简单的设计并非我们想象的那么简单.除了我们可以不太考虑以后解决的问题外,简单,作为一种重要审美标准.不是轻易能够达到的.我们一开始的设计往往不能够达到这样的要求,可能是类的划分不合理,可能存在着重复的代码,可能行为的分配需要调整,要达到简单,你就必须Refactoring你的代码,使设计更加合理. Refactoring如何支持简单的upfront设计? 随着时间的推移,越来越多的需求被实现,系统变得越来越大,某一天,你发觉系统有些变样,你觉得有必要修改系统的某一部分结构。这时候,你和你的同僚可能需要暂时摘下增加功能的帽子。你们可能需要更多的讨论,交流,使用任何有意义的方法为你们的讨论增强交流的效果,白板、CRC,然后进行Refactoring。 Kent Beck指出,某些大的Refactoring可能并不是一天就能完成的。它可能要花费几天甚至一个月的时间。但是,你还需要继续完成用户的需求。这时采取的方法就是小步的增量改变。在实现新需求,写下新的test case时,你可能看到一个机会能够让你的代码朝着大目标前进一步。使用这样的方法,每一次你的工作可能是移动一个变量或一个方法。但是随着新功能进一步的加入,这样的机会不断出现。最终,一个大的目标会变成很小的工作。这时候,你已经水到渠成,花上几分钟就完成了。 Refactoring的这种工作方式可以大大减少UpFront的设计量,它同时使你的设计变为一种必要和需求的产物,这种为了更好地加入新需求所做的设计,更准确地反映了问题的本身。同时,它使得设计随着你对问题的进一步深入而逐渐变得更合理,随着你对新技术的掌握而变得聪明,这是一种进化的设计方法。 简单设计的要求 对设计模式和Refactoring,以及与之相关的Agile联盟方法学的研究给我一个很深刻的印象。一个方面,就像Martin Fowler指出,Agile方法学的提倡者往往也是模式社团的领导者。另一方面,如果你仔细体会一下Agile 宣言,你会发觉它们非常重视人的能力。在我自己组织小组试用XP的过程中也发现,XP对它的参与者有很高的要求。 我们已经看到,简单的设计并不是愚蠢的设计,相反,它们是极端聪明的设计。要实现简单的设计,你必须有丰富的知识。这里在于,简单设计提倡你不要太多考虑以后的具体需求本身,但它确实需要你考虑代码和设计的结构。在用Refactoring武装你的思想中,我已经讲到,如果你的设计结构完全不考虑以后的扩展,那么Refactoring也无能为力。 设计模式是面向对象社团对设计的一个重要总结。其实,它们也是对简单设计的最好诠释。因为,好的设计模式,就是对于解决问题所能发现的最简单、最易重用的设计结构。另一方面,使用广为人知的设计模式也使得你的代码更容易为人理解、接受,大大提高了交流的效率、宽度和深度。 从这种意义上将,Refactoring不但没有排除设计模式,它更促进了设计模式的学习、发现和验证,以及更广泛的应用。Agile对程序员素质的要求,在这一点上也得到了充分的体现。一旦小组成员具备熟练运用设计模式的能力,你的设计可能就是一句话:用XXX模式。(在我领导的EOSP2P项目中,这个模式就是半State/半策略模式。当我说出这个模式时,我们发觉整个小组不再需要upfront设计,我们构建,然后Refactoring。当然,象Factory method模式几乎使每个系统都应当使用的。还有后续的proxy模式,都让设计变得如此轻松)。 但是,在应用设计模式的过程中,你必须时时记住简单性这个原则。设计模式最好的应用方法就是从简单开始,只要能够解决问题,抓住这种模式的核心,那么模式良好的结构保证你的Refactoring建立在一个坚实的基础之上。 所以,为了使Refactoring能够更好地进行,你需要更多的学习模式,因为设计模式不但是良好设计的开始,同时也是Refactoring的目标。 Refactoring的目标 作为OO社团最重要贡献之一的设计模式能够为Refactoring提供一个明确的目标。在Ralph Johnson多篇关于Refactoring的论文中都提到了这个问题。Martin Fowler的《Refactoring》中明确指出的就有state、strategy、visitor等模式。更深入一步,设计模式不但是Refactoring的目标,同时也为看起来有些零散的行为提供了一个全局性的指引。 Christopher Alexander在《The Timeless way of Building》一书中对模式的定义如下: Each pattern is a three-part rule, which expression a relation between a certain context, a problem, and a solution. 很多人在软件领域对他的定义做了修改和扩展,但这三部分内容是不变的。把这个定义放到我们软件上下文来看,设计模式有两个重要特性:
正因如此,很多人都把设计模式称之为micro-architecture。显然,由于它的可操作性,它可以通过Refactoring的方法达到。同时由于它一定层次的理论和总结性,他可以用于引导Refactoring并作为Refactoring的目标。
|