图书前言

第一版前言

“重构”最初是在Smalltalk圈子中构思出来的,但不久之后就进入了其他编程语言的阵营。由于重构是框架开发不可或缺的一部分,因此“框架工作者”在谈论他们的技术时,经常会提到“重构”一词。当他们改进类层次结构时,当他们需要删除多少行代码时,都会提到这个术语。框架工作者深知,好的框架不是一蹴而就的—随着经验的积累,框架需要不断地进行优化和调整。他们还知道,代码的读取和修改频率高于新代码的编写频率。保持代码可读性和易修改性的关键就是重构—特别是对于框架而言。当然,对于一般软件而言也是如此。

那么,重构有什么弊端吗?当然,“重构是有风险的”。它需要修改代码,这可能会引入细微的Bug。如果重构做得不正确,可能会导致代码回滚到数周之前的版本。此外,非正式或临时实施重构时,风险会更大。重构是基于对代码的深入研究的,研究的过程中你会发现待重构的点,研究越深入,发现的待重构的点就越多……做出的改变也就越多。最终,你会陷入一个无法自拔的深渊。为了避免自掘坟墓,必须系统地进行重构。当我和合著者撰写《设计模式》时,曾提到:“设计模式为重构提供了目标。”但是,确定目标只是问题的一部分;转换代码以达到目标则是另一个挑战。

Martin Fowler和其他作者通过阐明重构过程,为面向对象的软件开发做出了宝贵的贡献。本书解释了重构的原则和最佳实践,并指出了何时何地应该开始深入研究代码以改进它。本书的核心是一份全面的重构目录。每个重构都描述了经过验证的代码转换的动机和做法。一些重构手法(例如提取方法或移动字段)可能看起来会很明显。

理解此类重构的机制是以规范的方式进行重构的关键。本书中的重构将帮助你一步一步地更改代码,从而降低设计演变的风险。你可以将这些重构及其名称添加到开发工具书中。

我第一次体验规范的、一步一步的重构是在与Kent Beck在30000英尺的高空进行结对编程时(后文会提及在飞机上相遇),它确保我们一步一步地应用本书目录中的重构手法。这种规范的重构所带来的效果让我感到惊讶,我不仅对最终代码的信心增加了,而且感觉压力也减轻了。我强烈建议读者尝试这些重构:“您和您的代码都会变得更好。”

—Erich Gamma, Object Technology International, Inc.

1999年1月

第二版前言

曾经有一位技术顾问查看了一个开发项目的代码,当他浏览系统中心的类层次结构时,发现其设计得相当混乱。较高级别的类(父类)对一些函数的工作方式做出了某些假设,希望子类可以直接使用这些函数。然而,这些代码并不适合所有的子类,多数子类会选择覆写这些函数,只要对这些函数稍微进行修改就能避免子类的覆写。也有一些地方,父类的函数无法清晰地表达函数的功能意图,并且存在很多重复代码。还有一些地方,几个子类对可以明显向上移动的代码(公共代码)做了重复的事情。

顾问建议项目管理层查看并清理代码,但项目管理层并未重视这个建议。代码既然可以工作,而且项目进度紧张,经理们就会敷衍地说,会在稍后的某个时候处理这个问题。

顾问还向负责开发的程序员展示了这些问题,程序员们还是很敏锐的,了解了问题所在。这其实不是程序员的错,有时,程序员需要一双新的眼睛来发现问题。因此,程序员花了一两天的时间对层次结构进行了优化。完成后,他们删除了层次结构中一半的代码,依然能够满足项目需求。他们对结果很满意,发现添加新类和在系统其他部分使用这些类都变得更快、更容易了。

可项目管理层不满意,时间很紧,还有很多工作要做,这两位程序员花了两天时间做的工作,对系统几个月后必须提供的众多功能没有任何帮助,而且旧代码运行得很好……管理层认为,唯一的好处仅是设计“纯粹”了一点儿,代码“干净”了一点儿。管理层认为,项目必须交付能运行的代码,而不是让学者满意的代码。顾问建议对系统的其他核心部分进行类似的清理,这可能会使项目暂停一两周,但管理层依旧表示:“所有这些都只是为了让代码看起来更好,对项目交付没有任何好处。”

大家对这个故事有什么感想?顾问建议进一步清理是正确的吗?还是遵循那句古老的工程格言,“如果能运行,就不要修改它?”

我必须承认我有些偏见,我就是那位顾问。6个月后,该项目失败了,很大程度上是因为代码太复杂,无法调试或调整到可被接受的性能。

新的顾问Kent Beck被请来重启该项目—这项工作涉及从头开始重写几乎整个系统。他做了几件不同的事情,但最重要的变化之一是坚持使用重构不断清理代码。团队效率的提高以及重构所起的作用激励我编写了本书的第一版—这样我就可以传递Kent和其他人通过使用重构来提高软件质量所获得的知识。

从那时起,重构就成为编程词汇中公认的一部分,而原著也经受住了考验。然而,对于一本编程书来说,18年已经过时了,所以我觉得是时候重写它了。这样做让我重写了书中的几乎每一页,但从某种意义上说,几乎没有什么变化,因为重构的本质是一样的,至少大多数关键的重构手法本质上依旧保持不变。但我确实希望此次的重写能帮助更多的人学会如何有效地进行重构。

什么是重构

重构是改变软件系统的过程。重构不会改变代码的外部行为,但会改善其内部结构。这是一种规范的代码清理方法,可最大限度地减少引入错误的机会。本质上,重构是一种在编写代码后改进代码的设计。

“编写代码后改进设计”,这是一个奇怪的措辞。在软件开发的大部分历史中,大多数人认为我们先设计,只有设计完成后我们才能编码。随着时间的推移,代码将被修改,系统的完整性(根据该设计的结构)会逐渐消失。代码慢慢地从工程沦为易被黑客入侵的代码。

重构与这种做法相反。通过重构,我们可以将糟糕的甚至混乱的设计重新加工成结构良好的代码。每个步骤都很简单,甚至过于简单。我将字段从一个类移到另一个类,将一些代码从方法中取出以使其成为自己的方法,或者将一些代码向上或向下推送到层次结构中。然而,这些小变化的累积效应可以从根本上改善设计。这与软件衰退的概念完全相反。

通过重构,工作平衡发生了变化。我发现设计不是在开发过程中某一阶段发生的,而是在开发过程中不断发生的。在构建系统的过程中,我学会了如何改进设计。这种互动的结果是一个程序的设计在开发过程中能够一直保持良好的状态。

这本书里有什么

本书是一本重构指南,是为程序开发从业者编写的。我的目的是向您展示如何以可控且高效的方式进行重构。本书想要介绍的重构方式是,不在代码中引入错误且有条不紊地改进其结构的方式。

传统上,一本书以介绍书籍的主题或定义开始,我原则上同意这一点,但我发现很难通过泛泛的讨论或定义来介绍重构,所以我从一个例子开始介绍。第1章采用一个具有一些常见设计缺陷的小程序,并将其重构为一个更易于理解和更改的程序。这将向您展示重构的过程和许多有用的重构手法。想了解某个重构的真正含义,可以阅读专门讲解该重构手法的关键章节。

在第2章中,我将介绍重构的一般原则、一些定义以及进行重构的原因,同时概括重构过程中的一些挑战。在第3章中,KentBeck帮助我描述了如何查找代码的异味以及如何通过重构来清除它们。测试在重构中起着非常重要的作用,因此第4章描述了如何在代码中构建测试。

本书的核心—重构清单贯穿了后续其他的章节。虽然这不是一个全面的清单,但它涵盖了大多数开发人员可能需要的关键重构手法。它源于我在20世纪90年代后期学习重构时所做的笔记,我现在仍在使用这些笔记。当我想做某件事时,例如阶段拆分法(详见6.11节),清单会提醒我如何以安全、循序渐进的方式去做。我希望这是本书中你会经常回顾的部分。

JavaScript案例

与软件开发的大多数技术领域一样,代码示例对于说明概念非常重要。但是,不同语言中的重构可能会有细微差别,有时,一种语言会迫使我注意某些特定的事情,但重构的核心元素仍保持不变。

我选择JavaScript来说明这些重构,因为我觉得这种语言对大多数人来说都是可读的。但是,无论你当前使用的是什么语言,你都不应该觉得很难适应重构。我尽量不使用该语言的任何较复杂的部分,因此你可以仅凭对JavaScript的粗略了解就能理解重构。我使用JavaScript不代表对该语言的任何宣传或认可。

虽然我使用JavaScript作为示例,但这并不意味着本书中的技术仅限于JavaScript。本书的第一版使用了Java,许多程序员发现它很有用,即使他们从未编写过一个Java类。我确实尝试过使用十几种不同的语言作为示例来说明这种普遍性,但我觉得这会让读者感到困惑。不过,这本书是为使用任何语言的程序员编写的。除了示例部分,我没有对该语言做任何假设。我希望读者能够吸收我的一般性评论并将其应用于正在使用的语言。事实上,我希望读者能够采用JavaScript示例并使其适应正在使用的语言。

这意味着,除了讨论具体示例外,当我讨论“类”“模块”“函数”等时,会使用这些术语的一般编程含义,而不是JavaScript语言模型的特定术语。

我使用JavaScript作为示例语言,也意味着我会尽量避免使用那些常规JavaScript程序员不太熟悉的JavaScript风格。这不是一本“JavaScript重构”书,相反,这是一本恰好使用JavaScript的通用重构书。有许多有趣的重构特定于JavaScript(例如从回调重构到承诺,再到异步/等待),但它们超出了本书的范围。

书籍受众群体

我把这本书的目标读者定位为专业程序员—以编写软件为生的人。书中的示例和讨论包含大量需要阅读和理解的代码。这些示例是用JavaScript编写的,但应该适用于大多数语言。我希望有一定经验的程序员来理解这本书的内容,但也不需要太多经验就可以理解。

虽然本书的主要目标是帮助程序员学习重构技术,但本书对于已经了解重构的人来说也很有价值—它可以用作教学辅助工具。在这本书中,我花了很多精力来解释各种重构的工作原理,因此,经验丰富的开发人员可以使用这些材料来指导他们的同事。

虽然本书的重点是代码重构,但重构对系统设计也有很大的影响,高级设计师和架构师必须了解重构的原则并在项目中使用它们,重构最好由受人尊敬且经验丰富的开发人员来介绍,这样的开发人员最能理解重构背后的原则,并将这些原则应用到特定的场景。当您使用JavaScript以外的语言时尤其应当如此,因为您必须将我给出的示例改编为其他语言。

以下是如何在不阅读所有内容的情况下充分利用本书的方法。

如果您想了解什么是重构,请阅读第1章示例。

如果您想了解为什么要重构,请阅读前两章。它们将告诉您什么是重构以及为什么要这样做。

如果您想找到应该重构的地方,请阅读第3章。它会告诉您需要重构的迹象。

如果您真的想进行重构,请完整阅读前四章,然后阅读书籍的目录,以大致了解书中的内容,您不必了解所有细节。当您真正需要进行重构时,再详细阅读重构章节并使用它。目录是一个参考,您可能不想一口气读完它。

编写本书的一个重要部分是命名各种重构手法。术语有助于我们沟通,因此当一个开发人员建议另一个开发人员将一些代码提取到函数中,或者将一些计算拆分为不同的阶段时,双方都能理解对提炼函数法(详见6.1节)和阶段拆分法(详见6.11节)的引用。这些词汇表还有助于选择自动重构。

在别人奠定的基础上继续发展

我首先要说的是,这本书让我受益匪浅,感谢那些在20世纪90年代开创了重构领域的人们。正是基于他们的知识,才激励并启发了我撰写本书的第一版,尽管已经过去了很多年,但我仍然要承认他们奠定的基础。理想情况下,他们中的一位应该撰写第一版,但最终我成了有时间和精力的人。

重构的两位早期主要支持者是Ward Cunningham和Kent Beck。他们在早期将其作为开发的基础,并调整了他们的开发流程。特别想要提一点,正是与Kent的合作让我看到了重构的重要性,这一灵感直接促成了我撰写本书。

Ralph Johnson领导着伊利诺伊大学香槟分校的一个团队,该团队以对面向对象技术的实际贡献而闻名。Ralph长期以来一直是重构的拥护者,他的几位学生也为该领域做出了重要贡献。比尔·奥普代克(Bill Opdyke)在他的博士论文中撰写了第一份关于重构的详细书面著作。约翰·布兰特(JohnBrant)和唐·罗伯茨(Don Roberts)不只写文字,还创建了第一个自动重构工具“重构浏览器”,用于重构Smalltalk程序。

自本书第一版出版以来,许多人推动了重构领域的发展。特别是那些将自动重构添加到开发工具中的人,给程序员的工作带来了巨大的便利。我很容易想当然地认为,我可以用一个简单的按键序列重命名一个广泛使用的函数,但这种便利依赖IDE团队的努力,他们的工作帮助了我们所有人。

致谢

即使有那么多研究可以借鉴,我仍需要很多帮助才能写这本书。第一版很大程度上借鉴了Kent Beck的经验和鼓励。他首先向我介绍了重构,鼓励我写笔记来记录重构,并帮助我将它们整理成完整的文章,同时,他提出了“代码异味”的想法。我常常觉得,如果我们不是在写极限编程的基础书,他写的第一版会比我写得更好。

我认识的所有技术书籍作者都提到技术审阅者给了他们很大的帮助。我们都写过有很多缺陷的作品,只有我们的同行作为审阅者才能发现。我自己不做太多技术审阅工作,部分原因是我认为自己不擅长这项工作,所以我非常钦佩那些接受这项工作的人。审阅别人的书甚至赚不到一分钱,所以这样做是一种慷慨的行为。

当我开始认真写这本书时,我创建了一个顾问邮件列表,以便他们给我反馈。随着我取得进展,我将新材料的草稿发送给这个小组并征求他们的反馈。我要感谢以下人员在邮件列表中发布他们的反馈:Arlo Belshee、Avdi Grimm、Beth Anders-Beck、Bill Wake、Brian Guthrie、Brian Marick、Chad Wathington、Dave Farley、David Rice、Don Roberts、Fred George、Giles Alexander、Greg Doench、Hugo Corbucci、Ivan Moore、James Shore、Jay Fields、Jessica Kerr、Joshua Kerievsky、Kevlin Henney、Luciano Ramalho、Marcos Brizeno、Michael Feathers、Patrick Kua、Pete Hodgson、Rebecca Parsons和Trisha Gee。

在这群人中,我特别想强调Beth Anders-Beck、James Shore和Pete Hodgson在JavaScript方面给予我的特别帮助。

在完成初稿后,我将其发送出去做进一步审查,因为我想让一些新人来审视整个草稿。William Chargin和Michael Hunger都提供了非常详细的审阅意见。我还从Bob Martin和Scott Davis那里得到了许多有用的评论。Bill Wake在邮件列表中对初稿进行了全面审阅,为我的书籍锦上添花。

Thought Works的同事不断为我的写作提供想法和反馈,无数的问题、评论和观察激发了我对这本书的思考和写作。在Thought Works工作的好处之一是,他们允许我花大量的时间进行写作。特别感谢我们的首席技术官Rebecca Parsons,他经常与我交谈,并提出新的想法。

在Pearson,Greg Doench是本书的策划编辑,负责书籍出版过程中的许多问题;Julie Nahil是本书的执行编辑。我很高兴再次与Dmitry Kirsanov合作进行文字编辑,与Alina Kirsanova合作进行排版和索引编制。