重构与模式(修订版)

978-7-115-29725-9
作者: 【美】Joshua Kerievsky
译者: 杨光刘基诚
编辑: 杨海玲

图书目录:

详情

本书开创性地深入揭示了重构与模式这两种软件开发关键技术之间的联系,说明了通过重构实现模式改善既有的设计往往优于在新的设计早期使用模式。本书不仅展示了一种应用模式和重构的创新方法,而且有助于读者结合实战深入理解重构和模式。

图书摘要

软件开发方法学精选系列

Refactoring to Patterns

[美]Joshua Kerievsky 著

杨光 刘基诚 译

重构与模式(修订版)

人民邮电出版社

北京

图书在版编目(CIP)数据

重构与模式/(美)科瑞福斯凯(Kerievsky,J.)著;杨光,刘基诚译.--修订本.--北京:人民邮电出版社,2012.11

(软件开发方法学精选系列)

书名原文:Refactoring to Patterns

ISBN 978-7-115-29725-9

Ⅰ.①重… Ⅱ.①科…②杨…③刘… Ⅲ.①软件开发—研究 Ⅳ.①TP311.52

中国版本图书馆CIP数据核字(2012)第242502号

内容提要

本书开创性地深入揭示了重构与模式这两种软件开发关键技术之间的联系,说明了通过重构实现模式改善既有的设计,往往优于在新的设计早期使用模式。本书不仅展示了一种应用模式和重构的创新方法,而且有助于读者透过实战深入理解重构和模式。书中讲述了27种重构方式。

本书适于面向对象软件开发人员阅读,也可作为高等学校计算机专业、软件工程专业师生的参考读物。

软件开发方法学精选系列

重构与模式(修订版)

◆著 [美]Joshua Kerievsky

译 杨光 刘基诚

责任编辑 杨海玲

◆人民邮电出版社出版发行  北京市崇文区夕照寺街14号

邮编 100061  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

三河市海波印务有限公司印刷

◆开本:800×1000 1/16

印张:20  2013年1月第1版

字数:444千字  2013年1月河北第1次印刷

著作权合同登记号 图字:01-2012-7106号

ISBN 978-7-115-29725-9

定价:55.00元

读者服务热线:(010)67132692 印装质量热线:(010)67129223

反盗版热线:(010)67171154

广告经营许可证:京崇工商广字第0021号

版权声明

Authorized translation from the English language edition,entitled:Refactoring to Patterns,978-0-321-21335-8 by Joshua Kerievsky,published by Pearson Education,Inc.,publishing as Addison-Wesley Professional,Copyright © 2005 Pearson Education,Inc.

All rights reserved.No part of this book may be reproduced or transmitted in any form or by any means,electronic or mechanical,including photocopying,recording or by any information storage retrieval system,without permission from Pearson Education,Inc.

CHINESE SIMPLIFIED language edition published by PEARSON EDUCATION ASIA LTD.and POSTS & TELECOM PRESS Copyright © 2012.

本书中文简体字版由Pearson Education Asia Ltd.授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

本书封面贴有Pearson Education(培生教育出版集团)激光防伪标签,无标签者不得销售。

版权所有,侵权必究。

其他

献给Tracy、Sasha和Sophia。

对本书的赞誉

“重构必须付诸实践,才能体现出其真正价值,而非仅仅作为一种抽象的智力练习。模式则记录了具有公认良好属性的程序结构。本书将两者完美地结合起来。如果想真正实践重构,我推荐你阅读本书并活学活用。”

——Kent Beck,软件开发方法学的泰斗,极限编程创始人,模式先驱

“在《设计模式》一书中,我们曾经提到,设计模式是重构的目标。本书终于证实我们所言不虚。除此之外,本书还能够加深读者对设计模式和重构两方面的领悟。”

——Erich Gamma,IBM公司Eclipse Java开发工具负责人,《设计模式》四作者之一,模式先驱

“现在,软件模式和敏捷开发之间的联系终于被人道破。”

——Ward Cunningham,极限编程创始人,模式先驱,Wiki发明者

“本书展示了一种应用模式的创新方法,将自上而下地使用设计模式与自下而上地揭示迭代式开发和持续重构结合起来。任何职业软件开发人员都应该使用这种方法,去寻找使用模式改进代码的新的可能。”

——Bobby Woolf,IBM公司WebSphere软件服务部门IT咨询专家,Enterprise Integration Patterns和The Design Patterns Smalltalk Companion作者之一

“Joshua Kerievsky通过一系列独树一帜的设计级重构,将重构提升到全新的层次。本书向开发人员展示了如何对设计进行改进,从而简化日常工作。本书是重构实践的珍贵参考书。”

——Sven Gorts,重构与敏捷开发布道者,比利时refactoring.be网站创始人

“本书是对《设计模式》一书的重构,可能意义还不仅限于此。在此之前,设计模式这一主题一直是作为静态和僵化的过程来阐述的,本书则将其看做是动态和灵活的,使模式的学习变成了一种试验、出错然后改正的人性化过程,从中读者能够理解到,优秀的设计并非一蹴而就——它们都经历了艰难和反思。Kerievsky 还重构了阐述方式本身,使其更加清晰,更容易接受。实际上,他解决了我在写作Thinking in Patterns一书中遇到的许多组织问题。本书透彻地介绍并结合了测试、重构和设计模式诸多方面,字里行间洋溢着叙述的轻松、良好的技术感觉和难得的真知灼见。”

——Bruce Eckel,Mindview公司总裁,《Java编程思想》和《C++编程思想》的作者

“我第一次见到 Joshua,就对他在理解、应用和教授设计模式上表现出来的热情留下了深刻印象。伟大的教师对自己教授的内容和如何与人分享都有这样的热情。我想 Joshua 不愧是一位伟大的教师,一位伟大的开发者,我们都从他的深刻洞察中获益良多。”

——Craig Larman,Valtech首席科学家,《UML和模式应用》和《敏捷迭代开发》的作者

“本书非常重要,不仅由于它为有条不紊地引入合适的模式以改进代码提供了循序渐进的指导,更重要的是,它教授了设计模式之下的原则。本书对于新入门和专家级的设计人员都同样适用。真是一本伟大的书。”

——Kyle Brown,IBM公司WebSphere软件服务部门,Enterprise JavaTM Programming with IBM® WebSphere®,Second Edition作者

“掌握一门手艺不仅仅要获得正确的工具,还需要学会高效地使用工具。本书阐释了如何将工业级的设计工具与艺术家的技巧融于一炉。”

——Russ Rufer,硅谷模式小组

“Joshua使用模式引导一步步小的重构,从而实现更大的目标,又通过重构将模式引入代码,从而对其做出改进。你将学会如何以渐进方式大大改善既有代码,而不是强制地去适应一个预先设计好的解决方案。随着代码不断改变,你将实现超越,看到更好的设计方案——是的,你将体验到这些。”

——Phil Goodwin,硅谷模式小组

译者序

设计模式和重构对我们来说早已不是什么陌生的字眼了。1994年,GoF的巨著《设计模式:可复用面向对象软件的基础》初次向世人展示了设计模式的魅力。2002年,Martin Fowler的《重构:改善既有代码的设计》则刮起了一阵重构的旋风。记得在《重构》刚刚出版的时候,软件开发界和评论界就赞扬它是一本与《设计模式》具有同等高度的图书。我相信本书的每一位读者都和我一样,早已收藏了这两本书,反复阅读,仔细品味,并从中获益匪浅。

设计模式代表了传统软件开发的思想:好的设计会产生好的软件,因此在实际开发之前,值得花时间去做一个全面而细致的设计。而重构则代表了敏捷软件开发的浪潮:软件并不是在一开始就可以设计得完美无缺的,因此可以先进行实际开发,然后通过对代码不断地进行小幅度的修改来改善其设计。这两种方式看似格格不入,但是它们在本质上都有一个相同的思想——设计很重要,只是两者达到良好设计的方法不同。从设计模式和重构第一天与开发人员见面开始,它们就注定是一对休戚相关的兄弟。现在,本书终于为人们架设了一道连接设计模式与重构的桥梁。

这段时间,我总在想这样一个问题:什么是设计模式?每一类编程语言都具有其自身的特性,就面向对象编程语言来说,其特性就是抽象、封装、继承和多态。同时,使用每一类编程语言开发软件时也都有一些设计准则,这些准则保证了软件的质量,即具有良好的设计。而设计模式则是广大软件开发人员总结出的开发经验和技巧,它们利用编程语言的特性,实现这些设计准则。因此,在有经验的软件设计师眼里,没有设计模式,只有设计准则。现在,本书作者告诉我们:重构是实现设计模式的一种手段,设计模式往往也是重构的目的。从某种意义上说,重构成全了设计模式,而设计模式度量了重构。需要注意的是,所谓“设计模式是重构的目的”,并不是说重构的结果一定是设计模式,有些情况下,重构恰恰是为了避免设计模式的过度使用。这是本书最值得关注的地方。

在准备写这篇译者序的时候,我总是觉得很为难,因为写译者序类似于写读后感,是要道出翻译过程中的特别感受,而我在翻译的过程中并没有什么特别突兀的触动。从本书的第一个重构直到最后一个,一切都显得那么自然;作者给出的每一个建议,每一个告诫,每一次小小的改动,给我的感觉都是水到渠成的。现在想想,其实重构的魅力就在于此,它就是每个软件开发人员自然而然应该做的事情。有句话叫“绚烂之极归于平淡”,用来形容重构,真是再合适不过了。

翻译从来就不是一件轻松的事,加之我完成本书翻译的日子都是在上海炎热的夏天中度过的。每当我汗流浃背地坐在计算机前,斟酌应该如何表达作者原意的时候,我都会从作者迸发的思维和精巧的话语中感受到一种平淡而又无穷的智慧。有趣的是,我接受本书翻译邀请的那天,Martin Fowler先生正好到上海做演讲,能在这位《重构》作者的演讲堂上接受到一本重构图书的翻译邀请,真是机缘巧合。

最后,我要谢谢我的父母。是他们给了我一个健康的身体,一个良好的生活环境,更重要的是全心全意的支持。没有他们就没有这一切。爸爸妈妈,我爱你们!

Ralph Johnson序

《设计模式》一书中叙述了使用模式的几种方式。有些人在编写任何代码之前,都要很早地为模式做计划,而有些人在编写了大量代码之后才开始添加模式。第二种使用模式的方式就是重构,因为是要在不增加系统特性或者不改变其外部行为的情况下改变系统的设计。有些人在程序中加入模式,只是因为觉得模式能够使程序更容易修改;更多人这样做,只是为了简化目前的设计。如果代码已经编写,这两种情形都是重构,因为前者是通过重构使修改更容易,后者则是通过重构在修改后进行整理。

虽然模式是在程序中能够看到的东西,但是模式也是一种程序转换。每个模式都可以通过展示模式应用前后程序的变化来进行解释。这是可以将模式看做重构的另一种方式。

遗憾的是,许多读者都忽视了设计模式和重构之间的联系。他们认为模式只是关乎设计,与代码无关。我想可能是设计模式这个名字误导了他们,可是《设计模式》一书中到处都是 C++代码,这一点应该也说明了模式与设计和代码都密切相关,而且添加模式通常都需要改变代码。

Joshua Kerievsky恰恰发现了这种联系。我初次遇到他的时候,他刚刚开始组织纽约市的设计模式学习小组。他介绍了通过“前后变化”——用例子说明模式对某个系统的影响,来学习模式的想法。在他富于感染力的热情号召下,他离开纽约市之前,小组已经发展到60多人,每月聚会数次。他开始通过客户现场培训、自己开班和因特网为各个公司教授模式课程,甚至还教其他人如何教授模式。

Joshua继而还成为一位极限编程实践者和教师。因此,由他来写一本书介绍设计模式与极限编程的核心实践之一——重构之间的联系,可以说是再合适不过了。重构与设计模式绝不是没有关系的;相反,它们密切相关。虽然本书中谈到的模式并不都来自我们的《设计模式》一书,但是都遵循书中的风格。本书说明了怎样让模式帮助我们设计,而又不必进行预先设计。

按本书中所教授的方法进行实践吧,这不仅能够提高你做出优秀设计的能力,也能够提高思考优秀设计的能力。

Ralph Johnson,伊利诺伊大学厄巴尼—尚佩恩分校教授,

《设计模式》四作者之一

Martin Fowler序

几年来,我参与了敏捷方法尤其是极限编程的宣传和推广活动。在此过程中,人们经常会问到,这些方法与我长期对设计模式的兴趣是怎样和平共处的。事实上,我还曾经听人说,在我鼓励人们重构和演进式设计时,实际上是在放弃自己以前关于分析模式和设计模式的作品中所讲述的观点。

这种说法其实并不确切。看看模式社区的那些主要成员,再看看敏捷方法和极限编程社区的主要成员,有很多人活跃在两个社区。事实上,模式和演进式设计从非常早的时期起就有着密切的关系。

Joshua Kerievsky正处在两个社区交集的核心。我初次遇到他的时候,他已经在纽约市组织了一个成功的模式学习小组。小组成员互相合作,研究不断涌现的设计模式文献。我很快认识到Joshua对设计模式的领悟力可以说是首屈一指的,我从倾听他的谈话中获得了许多真知灼见。所以,他后来成为一名极限编程的先锋,对我来说是意料之中的。他在第一次极限编程会议上关于模式和极限编程的论文是我的最爱之一。

正因为如此,如果说有什么人最适合写模式与重构之间联系,那应该非 Joshua 莫属了。这个主题我在《重构》一书中曾经有所涉及,但是并没有深入探讨,因为我想把篇幅集中于基本的重构上。本书极大地扩展了这一主题,非常详细地讨论如何发展出《设计模式》[DP]一书中大多数流行的模式,说明了不需要预先将它们设计到系统中,而是应该随着系统发展而逐步演变出来。

除了通过学习获得的有关这些重构的具体知识以外,本书还讲述了有关模式与重构的一般性知识。许多人都说过,重构是学习模式的一种更佳方式,因为可以在重构的演进步骤中看到问题和解决方案之间的互动。这些重构还进一步证实了一个重要事实:重构其实就是循序渐进地进行较大的修改。

因此我非常高兴能够将本书介绍给大家。我花了很长时间说服 Joshua 写一本书,这些努力最终促成了本书的诞生。我对这一成果非常满意,不知读者以为然否?

Martin Fowler

前言

本书主旨

本书讲述的是重构(改善既有代码设计的过程)与模式(针对反复出现的问题的经典解决方案)的结合。本书建议,使用模式来改善既有的设计,要优于在新的设计早期使用模式。这对于已经存在几年和几分钟的代码都同样适用。我们通过一系列低层次的设计转换,也就是重构,来应用模式,改进设计。

本书目的

撰写本书是为了帮助读者:

理解如何结合重构和模式;

用模式导向的重构(pattern-directed refactoring)改善既有代码的设计;

找出需要进行模式导向重构的代码段;

了解为什么使用模式来改善既有的设计要优于在新的设计早期使用模式。

为了实现这些目的,本书包含以下特色:

一个含有27种重构方式的目录;

示例以实战代码为基础,没有纯示意性的玩具代码;

模式的描述,包括实际的模式示例;

一组坏味[1](也就是问题),表示需要进行模式导向的重构;

实现同一模式的不同方式的示例;

就什么时候应该通过重构实现模式、趋向模式以及去除模式给出建议。

为了帮助个人和小组学习书中的27种重构,本书给出了学习顺序的建议。

读者对象

本书的读者是从事或者有兴趣改善既有代码设计的面向对象程序员。他们中很多人都在使用模式和重构,但是从来没有通过重构来实现模式。还有一些程序员对重构和模式知之甚少,但愿意了解更多相关内容。

本书对新项目开发(从头编写新的系统或者特性)和遗留开发(主要是维护遗留系统)都适用。

所需背景

本书要求读者熟悉紧耦合、松耦合等设计方面的概念,以及继承、多态、封装、组合、接口、抽象类和具体类、抽象方法和静态方法等面向对象方面的概念。

书中示例使用Java代码。我发现对于大多数面向对象程序员来说,Java代码都很容易读懂。我有意识地不使用那些Java独有的特性,因此无论你习惯于用C++、C#、Visual Basic .NET、Python、Ruby、Smalltalk,还是其他面向对象语言编程,都应该能够理解本书中的代码。

本书与Martin Fowler的经典著作《重构》[F]息息相关。该书中包含了许多低层次的重构,例如:

提炼函数(Extract Method)

提炼接口(Extract Interface)

提炼超类(Extract Superclass)

提炼子类(Extract Subclass)

函数上移(Pull Up Method)

搬移函数(Move Method)

函数改名(Rename Method)

《重构》一书中还有一些更复杂的重构,例如:

以委托取代继承(Replace Inheritance with Delegation)

以多态取代条件表达式(Replace Conditional with Polymorphism)

以子类取代类型码(Replace Type Code with Subclasses)

为理解本书中介绍的模式导向的重构,读者无需了解上面列出的所有重构;相反,可以跟随阐释这些重构的示例代码进行学习。但是,如果要获取阅读本书的最佳效果,我推荐你同时有一本《重构》在手。该书是无价的重构资源,而且对理解本书很有帮助。

我要讨论的模式来自经典图书《设计模式》[DP],还有 Kent Beck、Bobby Woolf等作者以及我本人的著作。我和同事们在实际项目中都实践了重构实现、重构趋向和重构去除这些模式。通过学习模式导向的重构,你将理解如何重构实现、重构趋向和重构去除本书中没有提到的模式。

阅读本书不必事先成为这些模式的专家,但是对模式有所了解当然会有帮助。为了帮助读者理解所讨论的模式,本书包含了一些简洁的模式总结、模式的UML略图和许多示例实现代码。要更详细地理解模式,我推荐你在学习本书的同时,也结合研读所引用的模式文献。

本书使用UML 2.0表示法。如果对UML不太熟悉的话,不要担心。我也只是知其大略而已。编写本书时,Fowler的《UML精粹》[Fowler,UD]一书常伴我左右,不时查阅。

如何使用本书

要概略地了解本书中的重构,可以从学习每个重构的总结(参见 5.1 节),以及每个重构中“动机”一节的“优点和缺点”开始。

要更深入地理解重构,应该研究每个重构的各个部分,但“做法”一节除外。“做法”一节比较特殊,其目的是通过建议应该遵循哪些低层次重构,帮助读者实现该重构。理解本书中的重构,并不需要阅读这一节。这一节更可能用作在实际重构时的参考。

本书和《重构》[F]所讨论的代码坏味(code smell),是识别设计问题和找到有助于解决问题的相关重构的一种有益方式。也可以查看本书和《重构》中的重构列表(按字母顺序排列),找到能够改进设计的重构。

本书记载的是使设计实现、趋向和去除模式的重构。为了帮助你找到着手的方向,3.4 节专门讲述这一主题。本书还有一个表列出了所有模式的名称和可以用于使设计实现、趋向和去除模式的重构。

本书历史

我从 1999 年开始动笔撰写本书。当时,有好几个因素都促使我为模式、重构和极限编程(extreme programming,XP)[Beck,XP]写点什么。首先,我非常吃惊地发现,XP文献中还没有提及模式。我因此撰写了一篇名为Patterns&XP(模式与XP)的论文[Kerievsky,PXP],在该文中我公开地讨论了这一问题,并就如何将软件开发界的这两大主题结合起来提出了一些建议。

其次,我知道Martin Fowler在《重构》[F]一书中只写到了几个“通过重构实现模式”,而且他明确表示,希望有人在此方面进一步写作。这看上去是一个很值得努力的目标。

最后,在我和同事教授的设计模式研讨班上,我注意到有些学员需要更多指导,才能决定何时应该在设计中实际地应用模式。知道模式是什么是一回事,而真正理解什么时候如何应用模式,就完全是另一回事了。我认为这些学员需要学习一些实际的案例,在这些案例中,在设计时应用模式能看到实实在在的效果,因此我开始将这种案例汇编成一个提纲。

当我开始撰写本书时,我遵循了Bruce Eckel[2]的优秀写作传统,将草稿在网上公开,听取人们的意见。网络真是一个好东西。许多人向我发来反馈,有建议,有鼓励,也有感谢。

随着书稿和想法的不断成熟,我开始在许多会议、讲座和Industrial Logic公司的“模式与重构”强化研讨班上讲授“通过重构实现模式”的主题。这使我获得了更多的改进建议,而且更多地了解到程序员理解这一主题需要些什么。

渐渐地,我认识到重构是审视模式的最佳方式,而且模式正是一系列低层次重构所能达到的最佳目标。

很幸运,书成稿之后,得到了许多经验丰富的专业人士的审阅,他们提出了很多改进建议。我会在致谢中提供有关他们的更多情况。

站在巨人肩上

1995 年的夏天,我走进书店,第一次见到了《设计模式》[DP]一书,并从此与模式结下不解之缘。我感谢4位作者Erich Gamma、Richard Helm(我还未曾谋面)、Ralph Johnson和John Vlissides编写了如此优秀的技术图书。他们在书中所表现出的睿智,使我大大提高了自己的软件设计水平。

大约在1996年,我在一次模式会议上遇到了 Martin Fowler,那时他还没有出名。这就是我们长期友谊的开始。如果Fowler(以及他的合作者Kent Beck、William Opdyke、John Brant和Don Roberts)没有写经典著作《重构》[F],我真地怀疑自己是否还能写出这本书。与《设计模式》一样,《重构》完全改变了我从事软件设计的方式。

我能够完成本书,全拜《设计模式》和《重构》的作者们的辛勤劳动所赐。对此我感激不尽。

致谢

我是如此幸运,有一位妻子在我写作本书期间全心全意地支持我。Tracy 是最棒的。我愿与她白头偕老。

我们的两个女儿Sasha和Sophia,都是在我写作本书期间出生的。我要感谢她们在爸爸写作时候表现出的耐心。

在20世纪70年代,我的父亲Bruce Kerievsky将我和哥哥带到工作场所,让我们画那些空调房中的巨大计算机。他还给我们看长长的绿色和白色的计算机清单,上面用巨大的字母写着我们的名字。这些都激励我进入了这个伟大的行业,谢谢父亲!

感谢家人之后,应该是技术方面的致谢了。

John Brant 对本书居功至伟。他和他的同事 Don Roberts 都位居世界上最渊博的重构专家之列。John审阅了本书手稿的4个版本,提出了很多想法,并鼓励我删去许多比较平淡的内容。他的真知灼见遍及目录中几乎所有重构“做法”部分的字里行间。Don虽然忙于其他的项目,未能投入更多精力,但是他复查了John的反馈意见,非常感谢。我还要感谢两位为本书题跋。

Martin Fowler在审阅和建议上用力甚勤,包括简化略图和澄清某些技术讨论。他帮助我改正了一些有问题的UML图,而且进行了更新以反映UML 2.0的变化。我很荣幸Martin选中本书作为他主编的签名系列之一,感谢他为本书作序。

Sven Gorts 下载了本书手稿的多个版本,发来数量惊人的经过深思熟虑的意见。他提出了许多有用的想法,使本书的内容、图和代码都有改进。

Somik Raha 在本书内容的提高上帮助很大。他的开源项目htmlparser,是在他完全掌握模式之前启动的,成了需要“通过重构实现模式”的代码宝库。Somik和我结对完成了其中的许多重构。由衷地感谢他的支持、鼓励和建议。

Eric Evans,《领域驱动设计》一书的作者,对本书手稿的早期版本提出了建议。我们在写书的过程中,经常在旧金山附近的咖啡厅会面。在那里我们共同写作、交换计算机,并评论对方的书稿。感谢Eric的反馈和友谊。

Chris Lopez,硅谷模式小组(SVPG)的成员,对书的内容、图和代码提出了大量极为详细和有用的建议。同时也感谢硅谷模式小组的其他成员。Chris对本书的细心审阅大大超出了常规。

Russ Rufer、Tracy Bialik和硅谷模式小组的其他程序员(包括 Ted Young、Phil Goodwin、Alan Harriman、Charlie Toland、Bob Evans、John Brewer、Jeff Miller、David Vydra、David W.Smith、Patrick Manion、John Wu、Debbie Utley、Carol Thistlethwaite、Ken Scott-Hlebek、Summer Misherghi和Siqing Zhang)多次开会,审阅本书较早和更成熟的版本。他们提出了大量好的建议,帮助我认识到哪些地方需要澄清、扩充和精简。特别感谢Russ为本书安排这么多会议,感谢Jeff为本书讨论录音。

Ralph Johnson和他领导的UIUC(伊利诺伊大学厄巴纳—尚佩恩分校)的模式阅读小组对本书手稿的早期版本提出了极为有用的反馈。这些反馈是用 MP3 文件记录下来的。我花了大量时间倾听他们讨论的录音,并采纳了许多建议。我尤其要感谢 Ralph、Brian Foote、Joseph Yoder和Brian Marick,感谢他们的关心和建议。我还要感谢小组里的其他人,我还不知道他们的名字。感谢Ralph为本书作序。

John Vlissides 以各种形式提供了极为有用的反馈,包括对本书草稿第一版的许多详尽的注释。他对我的工作鼓励有加,对此深表谢意。

Erich Gamma 为本书介绍性的内容以及重构提供了一些很棒的建议。

Kent Beck 审阅了本书中的许多重构,而且还提供了内联Singleton(6.6节)重构中的旁注。我非常感谢他在意大利Alghero召开XP2002会议期间与我结对编程,合作创造了State模式的重构。

Ward Cunningham也提供了内联Singleton(6.6节)重构的旁注,并对编排介绍性的内容提供了有益而且关键的建议。

Dirk Baumer(Eclipse开发自动重构的首席程序员)和Dmitry Lomov(IntelliJ开发自动重构的首席程序员)都为本书中的许多重构贡献了真知灼见和建议。

Kyle Brown 审阅了手稿较早的版本,提供了许多很好的意见。

Ken Shirriff 和 John Tangney 对本书手稿的很多版本都提供了大量富于想法的反馈。

Ken Thomases 指出了用类替换类型代码(9.1节)重构中“做法”的较早版本的一个严重错误。

Robert Hirshfeld 帮助阐明了将装饰功能搬移到Decorator(7.3节)重构较早版本中的做法。

Ron Jeffries 在extremeprogramming@yahoogroups.com上与我长篇大论地争论,帮助我澄清了本书中的一些内容。他还帮助我“重构”了本书介绍性内容中很难处理的一节中的文字。

Dmitri Kerievsky 帮助我润色了前言中的文字。

以下诸位也不断提供了许多有益的反馈:Gunjan Doshi、Jeff Grigg、Kaoru Hosokawa、Don Hinton、Andrew Swan、Erik Meade、Craig Demyanovich、Dave Hoover、Rob Mee和Alex Chaffee。

我还要感谢邮件列表refactoring@yahoogroups.com上讨论本书中重构的诸位的反馈。

我要感谢Industrial Logic公司各种课程、设计模式研讨班和测试与重构研讨班上的学员,他们也对本书中的重构提供了建议。其中许多人帮助我了解到书中哪些地方不够清楚,哪些地方讲得还不够。

我特别感谢编辑Paul Petralia,还有他的团队(Lisa Iarkowski、Faye Gemmellaro、John Fuller、Kim Arney Mulcahy、Chrysta Meadowbrooke、Rebecca Rider和Richard Evans)。当其他出版社也在争取出版本书时,是Paul煞费苦心地说服Addison-Wesley取得了出版权。对此我由衷地感谢。我阅读Addison-Wesley许多知名著作多年,本书能够成为其中一员我备感荣幸。在本书写作过程中,Paul成了我的朋友。在他不唠唠叨叨地催促我加紧完稿的时候,我们在一起谈孩子、打网球,度过了许多愉快和轻松的时光。谢谢Paul,有他这样的编辑真是幸运。

[1].本书将smell译为坏味,是借用了围棋术语。围棋中说味道不好或者有坏味,通常就是指感觉可能存在潜在的问题。——译者注

[2].Bruce Eckel是《Java编程思想》和《C++编程思想》的作者,他令人吃惊地将全书电子文件公开,结果却取得了巨大成功。——译者注

第1章 本书的写作缘由

软件模式的伟大之处,就在于它们传达了许多有用的设计思想。所以,在学习了大量模式之后,就理应成为非常优秀的软件设计人员,不是吗?当学习、使用了几十个模式后,我也曾这样认为。模式帮助我开发灵活的框架,帮助我构建坚固、可扩展的软件系统。但是几年后,我却发现自己在模式方面的知识和使用模式的方式总是使我在工作中犯过度设计的错误。

设计技术进一步提高之后,我发现自己使用模式的方式逐渐发生了变化:我开始“通过重构实现模式、趋向模式和去除模式(refactoring to,towards,and away from pattern)”,而不再是在预先(up-front)设计中使用模式,也不再过早地在代码中加入模式。这种使用模式的新方式来自于我对极限编程(XP)设计实践的采用,它帮助我既避免了过度设计,又不至于设计不足。

1.1 过度设计

所谓过度设计(over-engineering),是指代码的灵活性和复杂性超出所需。有些程序员之所以这样做,是因为他们相信自己知晓系统未来的需求。他们推断,最好今天就把方案设计得更灵活、更复杂,以适应明天的需求。这听上去很合理,但是别忘了,这需要你未卜先知。

如果预计错误,浪费的将是宝贵的时间和金钱。花费几天甚至几星期对设计方案进行微调,仅仅为了增加过度的灵活性或者不必要的复杂性,这种情况并不罕见,而且这样只会减少用来添加新功能、排除系统缺陷的时间。

如果预期中的需求根本不会成为现实,那么按此编写的代码又将怎样呢?删除是不现实的。删除这些代码并不方便,何况我们还指望着有一天它们能派上用场呢。无论原因如何,随着过度灵活、过分复杂的代码的堆积,你和团队中的其他程序员,尤其是那些新成员,就得在毫无必要的更庞大、更复杂的代码基础上工作了。

为了避免这一问题,人们决定分头负责系统的各个部分。这看似能使工作更容易,但是副作用又产生了。因为每个人都在自己的小天地里工作,很少看看别处的代码是否已经完成了自己需要的功能,最后生成大量重复的代码。

过度设计下的代码会影响生产率,因为当其他人接手一个过度设计的方案时,必须先花上一些时间了解设计中的许多微妙之处,然后才能自如地扩展或者维护它。

过度设计总在不知不觉之中出现,许多架构师和程序员在进行过度设计时甚至自己都不曾意识到。而当公司发现团队的生产率下降时,又很少有人知道是过度设计在作怪。

程序员之所以会过度设计,也许是因为他们不想受不良设计的羁绊。不良的设计可能会深深地融入代码之中,对其进行改进不啻严峻的挑战。我遇到过这种情况,所以使用模式预先进行设计对我的吸引力才会如此之大。

1.2 模式万灵丹

最初学习模式时,它们代表的是我很想精通的一种灵活、精妙甚至非常优雅的面向对象设计方法。完整地学习了无数的模式和模式语言之后,我用它们改进以前开发的系统,用它们构思将要开发的系统。其效果非常可观,我知道,自己的路子走对了。

然而,随着时间的推移,模式的强大使我开始对更简单的代码编写方式视而不见。只要遇到某个可以使用两三种不同方法进行的计算,我就会很快想到实现Strategy模式,而事实上,使用简单的条件表达式编程更加容易,也更加快捷,完全足够。

有一次,我对模式的走火入魔可以说是暴露无遗。在结对编程中,我和搭档编写了一个类,它实现了Java的TreeModel接口,在树型窗口部件(widget)中显示Spec对象的图形。代码能够工作,但是树型窗口部件通过调用Spec对象的toString()方法来显示它们,而该方法并不返回需要的Spec对象信息。我们不能修改Spec的toString()方法,因为系统的其他部分还要用到这个方法。我们只好慎重思考如何继续。和往常一样,我开始考虑哪个模式能够助我们一臂之力。脑子里浮现出 Decorator模式。我建议,按照这个模式用一个对象封装 Spec 对象,再重写(override)这个对象的 toString()方法。搭档对这条建议的反应使我大吃一惊:“在这里用Decorator模式?那不等于大炮打蚊子吗?”他的解决方案是,创建一个名为 NodeDisplay 的很小的类,将Spec对象作为其构造函数的参数,它的一个公共方法toString()包含了Spec对象的正确显示信息。NodeDisplay 类编写起来几乎花不了多少时间,因为它的代码不超过 10行。而我使用Decorator模式的解决方案至少需要50行代码,需要多次反复委托调用Spec对象。

这样的经验使我意识到,再也不能过多地考虑模式了,应该重新把精力放在短小、简单和直截了当的代码上。我走到了一个十字路口:我努力学习模式,想成为更优秀的软件设计师,然而,现在为了真正更上一层楼,我需要放弃对它们的依赖。

1.3 设计不足

设计不足比过度设计要常见得多。所谓设计不足(under-engineering),是指所开发的软件设计不良。其产生原因有如下几种:

程序员没有时间,没有抽出时间,或者时间不允许进行重构;

程序员在何为好的软件设计方面知识不足;

程序员被要求在既有系统中快速地添加新功能;

程序员被迫同时进行太多项目。

随着时间的推移,设计不足的软件将变成昂贵、难以维护甚至无法维护的大麻烦。Brian Foote和Joseph Yoder曾经创造了一种名为Big Ball of Mud(大泥球)的模式语言,他们是这样描述类似软件的。

数据结构的构造非常随意,甚至近乎不存在。任何东西都要与其他东西通信。所有重要的状态数据都可能是全局的。在状态信息被隔开的地方,需要通过错综复杂的后端通道杂乱地传递,以绕开系统的原有结构。

变量名和函数名信息量不足,甚至会起误导作用。函数可能使用大量全局变量以及定义模糊的冗长的参数列表。函数本身冗长、费解,完成多项毫无关联的任务。代码重复很多。控制流很难看清,难以找到来龙去脉。程序员的意图几乎无法理解。代码完全不可读,近乎难于破译的天书。代码中有许多经过多个维护者之手不断修修补补留下的明显印记,这些维护者几乎都没有理解自己的修补会造成怎样的后果。[Foote and Yoder,661]

虽然你开发的系统也许不会这么恐怖,但是很可能也曾经有过设计不足的时候。我知道自己肯定这样干过。迅速使代码运行起来是压倒一切的要求,而这往往伴随着巨大的压力,使我们无法改进既有代码的设计。有些情况下,我们会有意地不对代码进行改进,因为我们知道(或者自认为知道)软件的生命期不会太长。而另一些时候,是别人迫使我们不对代码进行改进,因为好心的经理会这样说:“不出事的地方就不用改了,这样我们公司能够更有竞争力,更可能在市场竞争中取胜。”

长期的设计不足,会使软件开发节奏变成“快、慢、更慢”,可能造成这样的后果:

(1)系统的1.0版很快就交付了,但是代码质量很差;

(2)系统的2.0版也交付了,但质量低劣的代码使我们慢了下来;

(3)在企图交付未来版本时,随着劣质代码的倍增,开发速度也越来越慢,最后人们对系统、程序员乃至使大家陷于这种境地的整个过程都失去了信心;

(4)到了4.0版时或者之后,我们意识到这样肯定不行,开始考虑推倒重来。

这种事情在我们的行业里司空见惯。它的代价非常高昂,而且会极大地降低企业本应具备的竞争力。幸运的是,我们还有更光明的道路可走。

1.4 测试驱动开发和持续重构

测试驱动开发[Beck,TDD]和持续重构,是极限编程诸多优秀实践中的两个,它们彻底改进了我开发软件的方式。我发现,这两个实践能够帮助我和公司降低过度设计和设计不足的几率,将时间用在按时地构造出高质量、功能丰富的代码上。

通过测试驱动开发(TDD)和持续重构,我们将编程变成一种对话[1],从而高效地使可以工作的代码不断演变。

问:编写一个测试,向系统提问。

答:编写代码通过这个测试,回答这一提问。

提炼:通过合并概念、去芜存菁、消除歧义,提炼你的回答。

反复:提出下一个问题,继续进行对话。

这种编程节奏使我耳目一新。通过使用测试驱动开发,我们再也不用先花大量时间仔细考虑一个设计,就能够应付系统的每个细枝末节了。现在,我可以用几秒钟或者几分钟,先让原始的功能正确地工作起来,然后再重构,使它不断演进,达到必需的复杂程度。

Kent Beck为测试驱动开发和持续重构创造了一句“咒语”:“红、绿、重构”。其中的“红”和“绿”是指在单元测试工具(比如 JUnit)中编写并运行一个测试时所看到的颜色。整个过程是下面这样的。

(1)红:创建一个测试,表示代码所要完成的任务。在编写的代码能够通过测试之前,测试将失败(显示红色)。

(2)绿:编写一些权宜代码,先通过测试(显示绿色)。这时,你用不着为难自己,非要给出没有重复、简单和清晰的设计。可以在测试通过、能够心安理得地尝试更好的设计之后,再逐步朝这个目标努力。

(3)重构:对已经通过测试的代码,改进其设计。

听上去就这么简单,测试驱动开发和持续重构使编程领域面目一新。那些缺乏经验的程序员可能会这样想:“什么?为还不存在的代码编写测试?编写的代码通过测试之后,还需要立即进行重构?这不就是那种浪费很大、杂乱无章的软件开发方式吗?”

实际上,事情恰恰相反。测试驱动开发和持续重构提供了一种精益、迭代和训练有素的编程风格,能够最大程度地有张有弛,提高生产率。Martin Fowler称之为“迅速而又从容不迫”[Beck,TDD],而Ward Cunningham则解释说,这种说法主要指的是持续分析和设计,与测试关系不大。

程序员需要从实践中学习测试驱动开发和持续重构的正确节奏。我认识的一位程序员 Tony Mobley 曾称这种开发风格为一次范型转变,其影响之巨,不亚于结构化程序设计到面向对象程序设计的转变。无论你需要多长时间来适应这种开发风格,一旦习惯之后,你将发现,再用其他任何方式开发成品代码,都会感觉奇怪、不舒服甚至非常业余。许多使用测试驱动开发和持续重构编程的人,都发现这种方式有助于:

保持较低的缺陷数量;

大胆地进行重构;

得到更简单、更优秀的代码;

编程时没有压力。

要了解测试驱动开发的细节,请研读Test-Driven Development[Beck,TDD]或者Test-Driven Development:A Practical Guide[Astels]两部著作。要对测试驱动开发有感性认识,可以参见本书的7.5.3节和6.5.2节。要了解如何持续重构,请研读《重构》[F]一书(尤其是第1章)以及本书中的重构内容。

1.5 重构与模式

我观察了自己和同事们在许多项目中重构的对象和方式。在使用《重构》[F]一书中描述的许多重构方法时,我们还发现模式有助于改进设计。很多次我们都是通过重构实现模式,或者通过趋向模式进行重构,小心翼翼地避免产生过分灵活或者过度复杂的方案。

深入研究了应用“模式导向的重构”的动机之后,我发现它和“实现低层次重构”的一般动机是一样的:减少或去除重复的地方,简化复杂之处,使代码更好地表达其意图。

但是,如果只学习某个设计模式的一部分,很容易忽视这种动机。例如,《设计模式》[DP]中的所有模式都包含一个名为“意图”的部分。《设计模式》的作者们是这样描述意图的:“意图是回答下列问题的简单陈述:设计模式是做什么的?它的基本原理和意图是什么?它解决的是什么样的特定设计问题?”[DP,6]话虽如此,但是许多设计模式的“意图”部分只是在说明模式解决的主要问题。相反,更多的注意力放在了“模式是做什么的”之上。

我们来看两个例子。

Template Method(模板方法)的意图

定义一个操作中算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以在不改变算法结构的情况下,重定义该算法的某些特定步骤。[DP,325]

State(状态)的意图

允许一个对象在其内部状态改变时改变自己的行为。对象看起来似乎修改了自己的类。[DP,315]

这些意图描述并没有说明 Template Method 有助于减少或者去掉类层次中各个子类里相似方法中的重复代码,也没有说明State 模式有助于简化复杂的有条件的状态改变逻辑。如果程序员学习了一个设计模式的所有部分,尤其是“适用性”部分,他们将了解到该模式所要解决的问题是什么。

但是,在设计中使用《设计模式》一书时,许多程序员,包括我自己,都是通过阅读模式的“意图”部分,确定这个模式是否适合当前的情况。这种选择模式的方法的有效性不如将设计问题与模式能够解决的问题进行比对。为什么呢?因为模式之所以存在,就是为了解决问题,所以要了解在某种情况下模式是否真的有所帮助,必须理解它们有助于解决什么问题。

重构方面的文献似乎比模式方面的文献更关注具体的设计问题。开始学习某个重构时,你会在书的第一页看到重构有助于解决何种问题。本书给出的“模式导向的重构”目录直接延续了《重构》一书中所开创的工作,其目的是帮助读者了解模式有助于解决哪些具体的问题。

本书架设了模式和重构之间的桥梁,但是,其实《设计模式》一书的作者们在其皇皇巨著的“结论”一章已经提到了这两者之间的联系:

我们的设计模式记录了许多重构产生的设计结构。……设计模式为你的重构提供了目标。[DP,354]

Martin Fowler在《重构》一书的开始也有类似的说明:

模式和重构之间存在着天然联系。模式是你想到达的目的地,而重构则是从其他地方抵达这个目的地的条条道路。[F,107]

1.6 演进式设计

今天,在对模式——这种“重构产生设计结构”已经非常熟悉之后,我了解到充分理解为什么要“通过重构实现模式或者重构趋向模式”,比理解应用模式的结果或者结果的实现细节更有价值。

如果想成为一名更优秀的软件设计师,了解优秀软件设计的演变过程比学习优秀设计本身更有价值,因为设计的演变过程中隐藏着真正的大智慧。演变所得到的设计结构当然也有帮助,但是不知道设计是怎么发展而来的,在下一个项目中你就很可能错误地应用,或者陷入过度设计的误区。

迄今为止,我们关于软件设计的文献更多地集中在讲授优秀的解决方案上,对这些解决方案的演变过程则重视不够。这种情况需要改变。正如伟大的诗人歌德说过的:“那些父辈们传下来的东西,如果你能拥有它,你就能重新得到它们。”重构方面的文献通过揭示优秀设计方案的演化过程,帮助我们更好地重新理解这些方案。

如果想发挥模式的最大效用,也必须这样做:将模式放到重构的背景中领会,而不是仅仅将模式视为与重构无关的可复用的要素。这恐怕就是我编写“模式导向的重构”目录的主要原因。

通过学习不断改进设计,你就能够成为一名更优秀的软件设计师,并且减少工作中过度设计和设计不足的情况。测试驱动开发和持续重构是演进式设计的关键实践。将“模式导向的重构”的概念注入如何重构的知识中,你会发现自己如有神助,能够不断地改进并得到优秀的设计。

[1].对话这个隐喻出自Kent Beck,借用了大哲学家苏格拉底的对话教学方式。编写测试代码就好像是向系统提问题,编写系统代码是为了回答问题,这样的对话不断反复,最后生成的就是我们所需要的系统。——译者注

相关图书

有限元基础与COMSOL案例分析
有限元基础与COMSOL案例分析
程序员的README
程序员的README
现代控制系统(第14版)
现代控制系统(第14版)
现代软件工程:如何高效构建软件
现代软件工程:如何高效构建软件
GitLab CI/CD 从入门到实战
GitLab CI/CD 从入门到实战
科学知识图谱:工具、方法与应用
科学知识图谱:工具、方法与应用

相关文章

相关课程