Refactoring和方法
石一楹 (shiyiying@hotmail.com) 浙江大学灵峰科技开发公司技术总监 2001 年 12 月
Refactoring不仅仅是一种编码的方法。他同时是一种设计方法。本文从软件工程学的演变来透视Refactoring的重要作用。同时,你也可以在这里看到Refactoring和最近出现的XP开发方法学的紧密关系。
增量迭代 自从有软件工程一说开始,大大小小出现了许多方法,其中一些还常常被我们挂在嘴边.这些软件方法中最著名的生命周期模型包括WaterFall,它是由Royce于1970年首先提出来的.一般来说,WaterFall方法把软件开发的过程严格地分为几个阶段,包括: Requirement phase、Specification phase、Design phase、Implementation phase、Integration phase,要注意测试并非一个独立的阶段,几乎在每个阶段都需要检查和测试。
应当说,WaterFall模型由它自己的很多优点。WaterFall强制每一阶段都必须产生所有的产品,只有当这些产品通过相关的审核后才能开始下一阶段的工作,而这些产品中相当重要的一部分就是每一阶段的详细文档。Specification文档、design文档、code文档和其他相关的文档,如数据库手册、用户指南等等是维护产品基本工具。有研究指出,大约70%的软件预算用于维护阶段。而WaterFall模型强制每一阶段必须有详细文档,所以,看起来WaterFall能使用这些文档大大削减这方面的开销。
但是,Waterfall模型受文档驱使的方式也可能成为一个缺点。WaterFall起始于需求阶段,一旦需求报告被用户承认,你就开始起草Specification文档,它指出了软件需要做什么东西。Specification文档通常很长、很详细、很直白,因此很容易让人看着生厌。用户通常都很难完全看懂软件的Specification文档,他几乎不可能判断你讲得是否正确,但通常他还是会签字的。
这里的问题是人和人之间的交流是否能够完全使用文档来实现,即使你能够使用更加高级的图形如UML图。对于这个问题,生物学家Maturana和Varela在生物系统中进行了研究,下面是他们在《The Tree of Life》一书中的答案: 。。。。交流的现象不依赖于被传递的内容,而是依赖于在接收者身上发生了什么。并且这是一件和"传递信息"极不相同的事情。
Alistair Cockburn在他2001年的新书《Agile Software Development》解释说,你可以想象人被一层薄膜所包围,在接受到外部刺激,被重构翻译成内部信号,从而引发内部活动。所以人真正所接受的信息取决于内部而不是外部。更浅显的例子也有cockburn给出,他说: 考虑一个具体的例子,某人冲进一个房子,用日文大叫"着火了!"。一个能讲日语的人接收到许多信息,并且立刻冲向出口。他身旁的另一个日本人,正好在睡觉,因此根本没有接收到任何信息。外部刺激没有转化成内部信号。一个不懂日语的人注意到有个人进来大叫,但是却没有从他的话语中接收到任何特别的信息。每一个人从大叫接收到的东西端依赖于他的内部条件。
在对这一声大喊的翻译过程中,一个日本人能够接受到大量信息而非日本人却不知所云的重要原因是大喊的人和那个在房间里的日本人有一个共同的经验:日语。软件需求获取者、Specification撰写者和用户之间往往不可能有这样的共同经验。
同样,如果光光依靠设计文档想使得设计者和开发人员之间能够达到很好的交流也是不可能的。文档有助于增进理解,但是它不可能达到完整的理解。Waterfall试图依赖非常详细的文档来接近交流的完美。事实上,对文档的理解并不依赖于文档的详细程度。而是依赖于文档的编写者和文档阅读者之间的共同经验。
如果我和你之间都具有设计模式的背景,那么我说一句:"使用visitor模式",可能比两个没有设计模式经验的人画上100个UML类图,写上1000句的详细文档更能说明问题。
这种共同经验的积累显然不可能一趋而就。当然,在一个软件公司内部,长期的合作可能使用设计者和开发者之间能够达到更高层的交流,譬如,在进行设计的时候,你说:"就像上次那个多语中问题一样处理"。那么程序员就根本不需要再多的设计信息也能够完全实现。
对于用户的需求而言,这种长期的合作关系是很少见的。因此,除非你对该用户的领域非常熟悉,熟悉到你早已了解用户的一举一动。否则,你不可能一次就完成设计,你对用户的理解和用户对你的理解,也就是用户和你之间的交流需要多次的反复才能达到比较完善的程度。
交流的问题不仅仅出现在人和人之间,一个人需要和所实现的目标进行交流。这就是我们所说的理解。不管是在需求阶段、标书阶段、设计阶段或是实现阶段,问题的解决要依赖于你对这个问题的理解程度。理解越深刻,问题的解决就与完美。而这样的理解是不可能一次达到的。因此,对软件开发的任何一个阶段,你都需要进行迭代,从而能够更好地解决问题。
Waterfall的另外一个问题就是用户在最终产品交付之前不能看到任何用户的东西。而用户对提交产品的看法最后既有可能是:"I know this is what I asked for, but it isn't really what I wanted。"
产生这种结果的原因是,因为用户通过纸上的文档理解到的东西远远不及真正能够使用的软件产品那么多。Waterfall对需求和Specification如此的依赖,直接就可能产生并非用户真正需要的产品。
为了解决这个问题,我们需要使用增量模型。不同于waterfall和快速原型模型,增量模型并不试图一次性地发布完全符合用户需求的软件。相反,最终的产品被划分为几个build,设计、开发、发布甚至是需求都按照一个一个build进行。任何一个阶段,用户都可以获得一个满足部分需求的实际可操作的软件。而不是象前面的模型一般,需要等待几个月、一年或几年才能等到一个它并不需要的产品。
增量模型同时能够适应用户的变化。变化是每一个成长中的用户组织固有的特点,改变的要求同时也是软件开发的一部分。这也是我们说"软件是软的"所包含的意义。从客户的财务观点来看,增量模型也使得他们不需要一次性花费大量的资金。如果在开发的过程中发现问题,用户也可以及时地测回资金。
增量模型同时也会出现问题。其中的一个难点就是每一个新发布的build必须能够平滑地集成到已经存在的系统中而不会破坏已经存在的东西。从这个角度来讲,一个增量模型下的设计可能需要比Waterfall更加复杂的设计。因为Waterfall一次性地考虑所有问题,它能够提前看到设计、实现中的所有问题(在他自己的范围之内),既然问题已经全部摆出,你就能够相对容易地解决问题。而增量模型则不同,在构建当前的build时,你不会去考虑以后的build。所以,以后的build可能需要当前build不可预料的支持,它也可能以不可预料的方式集成到当前的系统之中。
面向对象的方法在这一点上助益甚多。面向对象的基本原则之一就是Open-Closed原则。一个模块可以在发布之后被close,这样即使增加build也不会破坏原来的系统。同时,它通过各种面向对象特有的机制,多态和继承,使得系统能够以增量的方式平滑地集成。但是,要close,你必须保证你的设计有足够的能力对以后的模块open。而正象我们在最开始所说的一样,一次性的设计是不可能达到这样的目标。显然,你需要一种方法,能够在一个模块外部行为不变的情况下改变内部的结构,使得增量能够顺利地进行下去。Refactoring是增量迭代模型的自然解决方案和核心技术。
Moving Target 增量迭代模型在处理Moving Target问题上更加显示出特有的优势。所谓的Moving Target就是可能出现随着开发的进展,软件的需求不断发生变化。
现今高速的信息流通和市场竞争压力是产生这种现象的最主要原因。一个最具竞争力的企业就是最能快速相应变化的企业。适应变化是一个企业的核心竞争能力。而软件之所以能够在现今社会中占据如此重要的地位,就是它比其他人造物都更能适应变化。如果软件不能提供对变化最有力的支持,那么就没有什么其他的artifact能够做到了。
Moving Target被认为是软件工程中最难以解决的问题之一。这里,随着软件构建的进程,用户的需求不断地发生变化。这不但会使开发小组产生受挫感,同时极易产生结构奇差的软件。同时这样的变化也会极大地增加软件开发的成本。
也许,这个问题可以通过构建一个快速原型来解决,这样的话,不管用户改变需求有多快,只要最后用户对specification满意了,产品马上就可以开始构建。但实际上,没有什么东西能够阻止客户在核准了specification以后再次改变需求。原型的好处只不过是能够让用户看到能工作的软件,从而减少变化的数量和频率。但是如果客户愿意花钱,它天天想改变需求都可以。
显然,过多的过程控制难以解决这样的问题。过程控制的复杂性会减低对需求变化的响应速度,那么,是不是象某些软件工程书所写的,我们对这样的状况无能为力呢?
拥抱变化-Refactoring和XP 随着Kent Beck的名著《Extreme Programming Explained》的诞生,XP成为软件工程最热门的话题之一。同时,它也是各种争论的中心。
Kent Beck在XPE的前言中的第二句话就对XP给出了定义: XP is a lightweight methodology for small-to-medium-sized teams developing software in the face of vague or rapidly changing requirements。
这段话解释了XP的主题,这在后面的字里行间都可以看到: 。。。It reliance on an evolutionary design process that lasts as long as the system lasts. …... System goes sour - the software is successfully put into production, but after a couple of year the cost making changes or the defect rate rises so much the system must be replaced. …..Business misunderstood。。。 Business changes。。。
XP要解决的重要问题就是如何实现更好的交流,从而更加适应需求的变化。它允许用户的需求在任何时候、任何方向上发生变化。为了实现这一点,它没有和其他工程学方法一样,把重点放在过程上,相反更强调个人技巧和实践原则。
XP的基本原则包括:
- 快速反馈
- 假定简单化
- 增量改变
- 接受变化
- 高质量工作
为了实现这些原则,Kent Beck提出了它的极端说:
- 如果 code reviews是好的,我们就每时每刻review code (pair Programming)
- 如果测试是好的,每个人每时每刻进行测试 (单元测试),甚至客户也是如此 (功能测试)
- 如果设计是好的,我们让它变成每个人的日常事务 (refactoring)
- 如果简单化是好的,我们就让系统一直保持简单设计来支持它的当前功能 (the simplest thing that could possibly work)
- 如果体系结构是重要的,每个人每时每刻都定义和修整体系结构 (metaphor)
- 如果集成测试是重要的,那么一天之内我们集成并测试很多次 (continuous integration)
- 如果较短的迭代是好的,我们做真正、真正短到秒、分、小时的迭代,而不是一周、一月或一年 (the Planning Game)
我并不是想在这里讨论XP的好坏。但是,我觉得任何人,不管他是否觉得XP可行,有一点不可否认:XP的这些实践原则和程序技巧,是软件界长期以来的积累。即使你不采用XP作为你的开发方法学,这些原则和技巧仍然是每一个想成为优秀的程序和设计人员不可获缺的基本技能。这也是我对待UML的态度,你可以不使用RUP作为你的过程管理指导,但UML本身确实是软件行业对建模技术的一个总结。如果你觉得看图比看文字更清楚,那么UML还是有可取之处的(当然,有些人不喜欢看规规矩矩的图形,Kent Beck就是其中的一个)。
上面可以看到,Refactoring是XP实践的核心之一。不是那么显而易见,但也许更重要的是,Refactoring和其他的原则和实践之间存在着不可分割的联系。
Kent Beck指出,你不可能只为今天的代码做设计。不然,你的设计就会走入死胡同,系统就无法不断进化,从而无法适应需求的变化。但是,设计的复杂不但不能解决问题,而且增加了设计的成本,增加了理解的难度,更要命的是增加了扩展和修改的难度,同样使你的系统变得十分僵化。所以,简单的设计-Simple Design--是成功的关键。然而,简单的设计要能够适应未来的需要,必须有一种切实的方法能在以后克服现在的局限性。Refactoring是其中的关键技术之一。然而,这一点并不是轻而易举能够实现的。要使Refactoring能够真正达到这样的目标,你需要能够控制代码的所有权,不然,你就很难修改其他人的代码对你代码作出的引用,因此需要共同的代码所有权(collective ownership)。为了使Refactoring能够更快捷、方便地进行,你的代码必须有良好的风格和大家都同意的代码标准(coding standard).如果你们成对编程(pair Programming),那么你们就能更好地处理复杂的Refactoring,也使得Refactoring出错的可能性大大减小。更进一步,测试(testing)能够使得Refactoring更加安全。编码是一个团队的工作,所以你需要连续不断的集成(continuous integration),这样,当你的refactoring和别人的代码发生冲突的时候,你马上就可以知道,原先的代码和设计能够及时得到恢复。你还需要足够的休息(rest),这样你才会有更多的勇气,不怕犯错,把Refactoring进行到底。
|