重构 的定义:小步、行为保持、两顶帽子
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。