跳到主要内容

重构的定义:小步、行为保持、两顶帽子

1. 这一章讲什么

第 1 章看过了手术全程,这一章把「手感」钉成定义:什么叫重构、什么叫不是重构;为什么值得做;什么时候动手、什么时候忍住。这一章也是全书唯一讲「原则」的地方——后面六章讲手法,一章讲手法在团队里的生存环境。

2. 名词与动词:一条纪律性的定义

作者给「重构」两个词性(名词与动词)的定义。名词:对软件内部结构的一种调整,目的在不改变软件可观察行为的前提下,提高可理解性、降低修改成本。动词:使用一串重构手法,在行为保持的前提下调整结构1。所以他可能「花一两个小时进行重构(动词),其间使用几十个不同的重构(名词)」。

这个定义真正的牙齿在「小步」二字。行业的日常用语里,「重构」几乎泛指一切清理代码的行为;作者明确拒绝这种用法——他说的重构特指用大量微小且保持行为的步骤完成大改动。由此得到一条毒舌但极好用的判据:

如果有人说他们的代码在重构过程中有一两天时间不可用,基本上可以确定,他们做的事不是重构。2

道理是机械的:每一步都小,代码就「很少进入不可工作的状态」,即便重构半途而废,停下的那一刻代码也是能跑的3。行话里把「清理代码但不保证小步、不保证行为保持」的动作叫结构调整——它是上位概念,重构只是其中纪律最好的那一类。

「可观察行为」也不是绝对严格:提炼函数会改变调用栈,性能可能变;有些手法会改模块的接口(一个模块暴露给外部调用的那组函数);甚至重构中发现的 bug 也会被原样保留——行为一致到这个程度:「如果我在重构过程中发现了任何bug,重构完成后同样的bug应该仍然存在」4。还有一个容易混的邻居:性能优化。两者都改代码、都不改功能,差别只在目的——重构要的是「更容易理解、更容易修改」,快慢不管;性能优化只要快,哪怕代码变得更难懂(两者的配合方式,第 12 章讲)5

3. 两顶帽子:一次只做一件事

Kent Beck 的比喻:开发时你在两顶帽子之间切换。戴加功能的帽子:只加新代码,不动旧结构,靠新测试衡量进度;戴重构的帽子:只调结构,不加功能,除了接口变化绝不碰测试6。帽子可能十分钟内换好几次,但你任何时刻都清楚自己戴的是哪顶。

这个比喻背后是全书的另一个地基:重构推翻了「先设计后编码」的传统图景。设计不必一次性在一开始完成——「设计不是在一开始的完成的,而是在整个开发过程中逐渐浮现出来」7,第 1 章那句「在代码写好之后改进它的设计」说的就是这件事。

4. 为什么值得:四个理由,最后一个吞掉前三个

作者列了四个目的,一层比一层实际:

理由机制
改进设计没有重构,内部结构随「只为短期目的的修改」累积而流失;「代码结构的流失有累积效应」——越看不懂越守不住,越守不住烂得越快8。消除重复是主要抓手:同样的事只表述一次,「这正是优秀设计的根本」9
更易理解编程的一半读者是几个月后的另一位程序员(常常是你自己)。他花一周才能改的代码,「如果他理解了我的代码,这个修改原本只需一小时」10。作者自称懒人:故意不记自己写过的代码,把该记的写进代码里,「下班后还可以喝上两杯啤酒」11
帮找 bug对代码的理解直接转化为揪 bug 的能力;结构清楚了,隐藏的假设自己浮出来。作者自嘲引 Kent Beck:「我不是一个特别好的程序员,我只是一个有着一些特别好的习惯的还不错的程序员」12
提高开发速度前三条的合账:内部质量好的代码库,加功能越来越快;差的代码库,每个新功能都要先做「考古」13

第四条值得单独展开,因为它是全书立论的承重墙,作者给它起了名字:设计耐久性假说——投入精力改善内部设计,能延长「开发保持快速」的时间。作者诚实声明了证据等级:「我还无法科学地证明这个理论,所以我说它是一个“假说”」,支持它的是他本人和上百名优秀程序员的职业经验14。这个自我声明贯穿全书:凡是没有数据支撑的,都叫假说。

5. 什么时候动手

作者把「何时重构」拆成几个具体场景,没有一个需要立项:

  • 三次法则(Don Roberts):第一次只管做;第二次有点反感但忍了;第三次——重构。原话即「事不过三,三则重构」15
  • 预备性重构:加功能之前,先花几分钟把路铺平。典型动作:发现某个函数「提供了大部分功能但几个字面量不合适」,与其复制一份改几个值,不如把它参数化。Jessica Kerr 的比方:往东 100 公里,先往北开 20 公里上高速,反而快三倍16
  • 帮助理解的重构:需要想「这段代码到底在干什么」的时候,直接把答案重构进代码——改名、拆函数。理解写在脑子里会忘,写进代码里同事也看得见;而且这类小重构像「扫去窗上的尘埃」,常让你看见之前看不见的设计问题17
  • 捡垃圾式重构:顺路看见垃圾,好捡就捡,不好捡就记下来回头捡;「至少要让营地比你到达时」更干净——每次经过变好一点点,积少成多18
  • 复审时重构:对着别人的代码,与其口头建议「这里应该抽个函数」,不如直接改出来看效果;「不必想象代码应该是什么样,我可以真实看见」19

作者特意堵住一个常见误读:重构不是独立于编程的仪式,「你不会专门安排时间重构,正如你不会专门安排时间写if语句」20。项目计划上没有「重构时间」这个条目。两条补充边界:丑代码如果藏在接口后面、没人需要理解它,可以容忍;「如果重写比重构还容易,就别重构了」——这个判断没有公式,需要经验21

何时不该动还有一条反向澄清:重构不是还债,「漂亮的代码也需要很多重构」——昨天合理的权衡,今天加新功能时可能就不再合理22

6. 作者的判断与证据

判断证据
重构不是「银弹」,是「银钳子」——帮你始终良好地控制代码作者自拟的比喻,刻意调低定位23
设计耐久性假说作者明说无法科学证明,靠经验支撑(假说)
重构的目的是经济而非道德「重构的唯一目的就是让我们开发更快」;用「整洁的代码」做道德论证是陷阱(第 12 章展开)
Kent Beck 箴言:先让修改变容易,再做这次修改引用,与预备性重构同一立场24

判断(我们的,不是书里的): 「可观察行为不变」这条纪律的隐含前提是「行为可以被观察到」——所以测试网不是配套练习,而是定义的成立条件。没有测试网,你根本无从断言自己做的叫重构。如果错,会错在: 如果存在一种严格的数学等价性证明或足够可靠的自动化工具能代替测试保证行为不变,这个前提会松动;第 12 章会讲到,对有限的几个手法,工具确实能做到。

7. 边界与局限

  • 「重写还是重构」的判断,作者明说给不出简单建议,只说「不花时间尝试往往很难知道重构的难度」21
  • 假说就是假说:设计耐久性没有实验数据,只有从业者共识。
  • 本章不处理团队环境下的阻力(进度压力、代码所有权、分支模型)——那些是第 12 章的主题。
  • 名词/动词的严格定义与行业习惯用法长期冲突;作者自己也在第 2 章末尾承认「重构」一词的滥用已成事实。

8. 可带走的

  1. 判断是不是重构,看两点:行为保持吗?步子小到随时能停吗?
  2. 「一两天不可用」= 不是重构,是结构大手术,风险完全不同。
  3. 加功能与改结构分帽子做,一次还一件事的账。
  4. 重构的收益走的是经济账:找得快、改得快、错得少——不谈美感。
  5. 三次法则:第三次重复之前动手。
  6. 加功能前先铺路:几分钟的预备性重构,常比硬塞快。
  7. 不需要的丑代码可以不修;重写更容易时就别修。
  8. 「设计耐久性」是假说,引用时别说成定理。

9. 原文地图

主题原书章原文位置
名词/动词定义第2章 §2.1text/10-ch02.txt:11(搜「对软件内部结构的一种调整」) · text/10-ch02.txt:19(搜「一系列重构手法」)
一两天不可用判据第2章 §2.1text/10-ch02.txt:28(搜「有一两天时间不可用」)
行为保持的边界(bug 留存)第2章 §2.1text/10-ch02.txt:41(搜「同样的bug应该仍然存在」)
与性能优化之别第2章 §2.1text/10-ch02.txt:43(搜「重构与性能优化有很多相似之处」)
两顶帽子第2章 §2.2text/10-ch02.txt:49(搜「两顶帽子」)
银钳子第2章 §2.3text/10-ch02.txt:63(搜「银钳子」)
结构流失的累积效应第2章 §2.3text/10-ch02.txt:70(搜「累积效应」)
一小时与一周第2章 §2.3text/10-ch02.txt:87(搜「如果他理解了我的代码」)
设计耐久性假说第2章 §2.3text/10-ch02.txt:133(搜「设计耐久性假说」)
三次法则第2章 §2.4text/10-ch02.txt:147(搜「Don Roberts给了我一条准则」)
上高速的比方第2章 §2.4text/10-ch02.txt:162(搜「往东去100公里」)
扫去窗上的尘埃第2章 §2.4text/10-ch02.txt:188(搜「扫去窗上的尘埃」)
不专门安排时间第2章 §2.4text/10-ch02.txt:211(搜「专门安排时间重构」)
何时不该重构第2章 §2.4text/10-ch02.txt:307(搜「并不需要修改它」) · text/10-ch02.txt:311(搜「重写比重构还容易」)

Footnotes

  1. 出处:「重构的原则」第 11 段(text/10-ch02.txt:11,搜「对软件内部结构的一种调整」)与第 19 段(text/10-ch02.txt:19,搜「一系列重构手法」)。

  2. 出处:「重构的原则」第 28 段(text/10-ch02.txt:28,搜「有一两天时间不可用」)。

  3. 出处:「重构的原则」第 26 段(text/10-ch02.txt:26,搜「很少进入不可工作的状态」)。

  4. 出处:「重构的原则」第 41 段(text/10-ch02.txt:41,搜「同样的bug应该仍然存在」)。「可观察行为」的限定见第 36 段(text/10-ch02.txt:36,搜「可观察行为」)。

  5. 出处:「重构的原则」第 43 段(text/10-ch02.txt:43,搜「重构与性能优化有很多相似之处」)。

  6. 出处:「重构的原则」第 49 段(text/10-ch02.txt:49,搜「两顶帽子」)。

  7. 出处:「前言」第 60 段(text/07-fm.txt:60,搜「逐渐浮现出来」)。「在代码写好之后改进它的设计」见第 46 段(text/07-fm.txt:46,搜「在代码写好之后改进它的设计」)。

  8. 出处:「重构的原则」第 70 段(text/10-ch02.txt:70,搜「累积效应」)。

  9. 出处:「重构的原则」第 79 段(text/10-ch02.txt:79,搜「优秀设计的根本」)。

  10. 出处:「重构的原则」第 87 段(text/10-ch02.txt:87,搜「如果他理解了我的代码」)。

  11. 出处:「重构的原则」第 96 段(text/10-ch02.txt:96,搜「很懒惰的程序员」)与第 98 段(text/10-ch02.txt:98,搜「这样我就不必记住它」)。

  12. 出处:「重构的原则」第 109 段(text/10-ch02.txt:109,搜「还不错的程序员」)。

  13. 出处:「重构的原则」第 119 段(text/10-ch02.txt:119,搜「进展很快」)。代码库像补丁摞补丁、需要考古的描述在第 120-122 段(text/10-ch02.txt:120,搜「看起来就像补丁摞」)。

  14. 出处:「重构的原则」第 133 段(text/10-ch02.txt:133,搜「设计耐久性假说」)与第 134 段(text/10-ch02.txt:134,搜「还无法科学地证明这个理论」)。

  15. 出处:「重构的原则」第 150 段(text/10-ch02.txt:150,搜「三则重构」)。

  16. 出处:「重构的原则」第 162 段(text/10-ch02.txt:162,搜「往东去100公里」)。预备性重构的定义在第 154 段(text/10-ch02.txt:154,搜「添加新功能之前」)。

  17. 出处:「重构的原则」第 188 段(text/10-ch02.txt:188,搜「扫去窗上的尘埃」)。

  18. 出处:「重构的原则」第 201 段(text/10-ch02.txt:201,搜「至少要让营地比你到达时」)。

  19. 出处:「重构的原则」第 270 段(text/10-ch02.txt:270,搜「我可以真实看见」)。

  20. 出处:「重构的原则」第 211 段(text/10-ch02.txt:211,搜「专门安排时间重构」)。

  21. 出处:「重构的原则」第 311 段(text/10-ch02.txt:311,搜「重写比重构还容易」)。 2

  22. 出处:「重构的原则」第 218 段(text/10-ch02.txt:218,搜「漂亮的代码也需要很多重构」)。

  23. 出处:「重构的原则」第 63 段(text/10-ch02.txt:63,搜「银钳子」)。

  24. 出处:「重构的原则」第 223 段(text/10-ch02.txt:223,搜「首先令修改很容易」)。