重构与模式(修订版)(txt+pdf+epub+mobi电子书下载)


发布时间:2021-08-03 18:52:44

点击下载

作者:[美]JoshuaKerievsky著

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

重构与模式(修订版)

重构与模式(修订版)试读:

前言

本书主旨

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

本书目的

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

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

用模式导向的重构(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,借用了大哲学家苏格拉底的对话教学方式。编写测试代码就好像是向系统提问题,编写系统代码是为了回答问题,这样的对话不断反复,最后生成的就是我们所需要的系统。——译者注第2章重构

本章中我将就“何谓重构”和“需要怎样做才能善于重构”提出一些看法。本章最好和《重构》一书[F]中的“重构原则”(Principles in Refactoring)[1]一起阅读。2.1 何谓重构

重构就是一种“保持行为的转换”,或者如Martin Fowler定义的那样:“(重构)是一种对软件内部结构的改善,目的是在不改变软件的可见行为的情况下,使其更易理解,修改成本更低。”[F,53]

重构过程包括去除重复、简化复杂逻辑和澄清模糊的代码。重构时,需要对代码无情地针砭,以改进其设计。这种改进可能很小,小到只是改变一个变量名;也可能很大,大到合并两个类层次。

要保证重构的安全性,确保所做的修改不会产生任何破坏则必须手工测试或者运行自动测试。如果能够快速地运行自动测试,确保(修改后)代码仍能工作,你就能更大胆地进行重构,更乐于尝试试验性的设计。

循序渐进地进行重构有助于防止增加缺陷。大多数重构过程都需花费一些时间。有些大型重构可能需要持续数天、数周甚至数月,才能完成转换。但是即便这样的大型重构也是循序渐进地实现的。

重构最好是持续而不是分阶段地进行。只要看到代码需要改善,就改善它。但是,如果你的经理要求你完成某项功能,以备明天进行演示之用,那么当然应该先完成这项功能,以后再进行重构。持续重构能够很好地满足业务需求,但是重构实践必须和谐地适应业务上的轻重缓急。2.2 重构的动机

虽然我们对代码进行重构的原因很多,但是以下这些动机是最具普遍性的。

使新代码的增加更容易

在系统中增加新功能时,可以选择快速编写出这个功能,而不考虑它是否能很好地适应原有设计,也可以选择对原有设计进行修改,从而能够容易和从容地接纳新功能。如果选择前者,就会导致所谓的设计欠账(参见 2.7 节),迟早还是要通过重构来偿还;如果选择后者,则需先分析为了更好地接纳新功能,需进行哪些修改,然后再进行必要的修改。这两种选择无所谓好坏。如果时间紧张,可能应该先快速地添加新功能,以后再进行重构;如果时间充裕,或者你认为在编写新功能之前先为它做好准备更快,那么就尽管在增加新功能之前进行重构。

改善既有代码的设计

通过持续改善代码的设计,代码将越来越容易处理。这与通常所见情况——重构很少,大量精力都花在快速而短视地增加新功能上,形成鲜明的对比。持续重构包括不断地嗅探代码的坏味(参见第4章),一旦发现坏味就立即(或者很快)去除。如果能够养成持续重构的“卫生”习惯,你将发现,代码的扩展和维护更加容易,从而更加享受工作。

对代码理解更透彻

有时,我们读代码时会对它的功能和机理毫无头绪。就算现在有人能够站在旁边进行解说,以后别人再来阅读这段代码时,还是会一头雾水。对这种代码是否该加一些注释呢?非也。如果代码本身不清晰,就说明存在坏味,需要通过重构清除,而不是用注释来掩饰。

在重构这种代码时,如果有完全理解代码的人在场是最好不过了。如果无法到场,看看他能否通过电子邮件、即时通信或者电话进行解释。如果这也不行,那就先重构你能够理解的部分。最终,随着重构的进行,这段代码会越来越容易理解。

提高编程的趣味性

我经常反躬自问是什么促使自己重构代码。当然,我会说重构能够拨冗删繁,能够简化或者澄清代码。可真正促使我进行重构的是什么呢?情绪。我之所以经常重构,只是要使编写的代码不那么讨厌!

我曾经参与了一个存在很多重大设计欠账的项目。尤其是其中有一个职责过多的巨类。因为我们的工作很大程度就是在修改这个巨类,所以每次签入(check in)代码(这是经常的事情,因为我们采用了持续集成),都不得不处理涉及这个巨类的复杂合并。结果,每个人都不得不花更多的时间集成代码。这真让人讨厌!因此我和另一位程序员花了3个星期,将这个巨类拆分为多个小类。这是一项艰苦的工作,但是不得不做。大功告成之后,代码的集成时间大大缩短,整个团队的编程体验也大大改善。2.3 众目睽睽

当《独立宣言》还在起草时,本杰明·富兰克林坐在托马斯·杰弗逊的身边,把杰弗逊关于“我们认为这些真理是神圣的,毋庸置疑”的措词修改为现在非常著名的句子“我们认为这些真理是不言而喻的”。根据传记作家Walter Isaacson的说法,杰弗逊对富兰克林的改动暴怒不已。富兰克林意识到朋友的情绪很激动,所以给他讲述了另一位朋友约翰·汤普森(John Thompson)的故事。

约翰·汤普森刚刚开始从事制帽业,想为自己的公司设计一个标记。他设计出如下图所示的标记:John Thompson,帽商,制作和销售帽子,现金支付

在启用新标记前,约翰决定给几个朋友看看,听听他们的意见。第一个朋友觉得“帽商”这个词有些重复,没有必要,因为后面的话“制作……帽子”,已经说明了约翰是一个帽商。于是“帽商”这个词被删除了。第二个朋友认为,“制作”一词可以不要,因为顾客不会关心到底是谁制作了帽子。于是“制作”一词也被删去。第三个朋友说,他认为“现金支付”毫无用处,因为不会有顾客赊账买帽子,一般人们都会用现金来买。所以这些词也被删去。

现在标记变成了:“John Thompson销售帽子。”“销售帽子!”他的另一个朋友说,“哎呀,没人认为你会给他发帽子的。这个词有什么用处呢?”于是“销售”被删去了。这时“帽子”这个词显然也没什么用处了,因为标记里已经有了帽子的图形。所以标记最终被简化成:John Thompson

在Simple and Direct一书中,Jacques Barzun阐释到,所有优秀的著述,都是不断修改而成的[Barzun,227]。他指出,修改意味着重新审视。John Thompson的标记在他的朋友们不断修改之下,去除了重复的文字,简化了语言,澄清了意图。富兰克林之所以要修改杰弗逊的句子,是因为他看到有更简明、更佳的方式来表达杰弗逊的意图。某个人的工作如果能经过多个人进行修改,将得到显著的改进。

这对代码而言亦然。要得到最佳的重构结果,需要多人的帮助。这正是极限编程建议采用结对编程和代码集体所有这两种实践的原因之一[Beck,XP]。2.4 可读性好的代码

我常常遇到一些给我留下深刻印象的代码,以至于我会在数月乃至数年中不断地向人们讲述。我研究Ward Cunningham[2]所写的一段代码时,就碰到这种情况。也许有读者尚不知道Ward是何许人也,但是应该知道他诸多杰出的创新。Ward创造了CRC(Class-Responsibility-Collaboration,类—职责—协作)卡、Wiki Web(维基网站,一种简单快速的可读写网站)、极限编程和FIT测试框架(http://fit.c2.com)。

我所研究的代码来自某个重构研讨班上使用的虚构的工资系统。作为这个研讨班的教师之一,我需要在教学之前先对代码研究一番。我先浏览了测试代码。研究的第一个测试方法是根据日期检查工资额。立即映入眼帘的是日期。代码是这样写的:

这行代码调用了以下方法:

我既惊又喜。即使是在测试代码中,Ward 也尽心竭力地编写了可读性极佳的方法。如果他不这么费心写出如此简单、容易理解的代码,完全可以写成这样:

虽然上面的代码也能产生同样的日期,但是它无法完全像Ward的november()方法那样做到以下两点:

读起来像自然语言;

将重要代码与分散注意力的代码分离开来。

我再来讲一个与此大相径庭的故事。有一个名叫w44()的方法。我是在为一家大型华尔街银行开发的贷款风险估算程序(一堆Turbo Pascal 大杂烩代码)中发现这个w44()方法的。当时,我刚刚开始自己的职业程序员生涯,最初的三个星期都花在研究这个代码沼泽上。终于,我弄明白这里的“44”是逗号的ASCII 码,而“w”则代表“with”。所以这位程序员的w44()是指其例程返回的是一个数字,格式化为一个带[3]逗号的字符串。这可真够直观的!我怀疑这位程序员如果不是确实想不出其他好名字,就肯定是想让别人无法接手,以保住自己的铁饭碗。

Martin Fowler说得好:

任何傻瓜都会编写计算机能理解的代码。好的程序员能够编写人能够理解的代码。[F,15]2.5 保持清晰

保持代码清晰类似于保持房间整洁。一旦你的房间变得乱七八糟,就更难清理了。变得越乱,就越不想清理。

假定你对房间进行了大扫除。接下来该怎么办呢?如果想保持房间整洁,就不能把东西(比如那些臭袜子)扔在地板上,或者让书、杂志、眼镜和玩具堆在桌面上。必须保持卫生。

你经历过这些吗?我经历过。如果几个星期之内都能保持房间整洁,那么保持卫生就开始成为一种习惯,以后就不会再为应该把臭袜子扔在地板上还是收到洗衣篮里进行思想斗争了。我的习惯会驱使着我将袜子收进洗衣篮。

糟糕的是,新习惯往往有让步于旧习惯的危险。比如,哪天你累得已经顾不上收拾地板上的衣服,然后又有几本书被一个蹒跚学步的孩子从书架上碰到桌上。在你意识到之前,你的房间又归于一片狼藉。

要保持代码清晰,必须持续地去除重复,简化和澄清代码。决不能容忍代码中的脏乱,决不能倒退到坏习惯中去。清晰的代码能产生更好的设计,而更好的设计将使开发过程更加迅速,从而会使客户和程序员皆大欢喜。请保持代码的清晰吧!

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载