笔记
← 返回《重构-改善既有代码的设计》系列目录 NOTES

02|为什么「不改变行为」是重构的铁律?

如果重构什么功能都没改,那它到底改了什么?以及一个更关键的问题:这条「什么都不改」的规矩,为什么一步都不能破?

02|为什么「不改变行为」是重构的铁律?.md 更新于 2026-09-23 ~9 分钟阅读

一、先说结论

要点

福勒的答案很直接:因为「不改变行为」是重构唯一可靠的验收标准。

一旦允许行为在重构过程中发生变化,你就再也分不清「我把结构改好了」和「我把功能改坏了」——因为你连「改没改坏」都无从判断。行为不变,才让每一次结构改动都变得可验证、可回滚、可放心地反复做。

所以这条铁律不是形式主义,它是重构与「随手改代码」之间唯一清晰的分界线。跨过这条线,做的还是别的事,只是借用了「重构」这个名字。


二、我们先理解一个问题

先看一个很常见的场景。

一个工程师对同事说:「我把订单模块重构了一遍。」同事问:「功能还和以前一样吗?」他说:「基本一样,就是把支付回调里的那个小逻辑顺便修了一下。」

这时,问题就来了。

「基本一样」到底是一样还是不一样?那个「顺便修的小逻辑」是有意为之,还是不小心碰到的?如果明天线上出了支付问题,谁能确定是这次重构引入的,还是本来就有的?

这个场景暴露的,是重构最核心也最容易被忽视的一环:


三、作者到底提出了什么观点?

福勒的观点可以拆成两层。

第一层:重构只动结构,不动行为。 他用的是「可观察行为」这个词——也就是说,从系统外部看,输入同样的数据、触发同样的动作,得到的反馈应当完全一致。内部怎么重排、怎么改名、怎么拆函数,都可以;但对外表现,必须原样不变。

第二层:这不是建议,是前置条件。 福勒认为,行为不变是重构能够安全进行的前提。少了这个前提,你做的每一次改动都同时押上了「结构」和「功能」两笔赌注,而你并没有办法分别检验它们。

换个说法:


四、把这个观点翻译成人话

用装修房子来理解最清楚。

你住的房子旧了,想改造一下。有一类改动是可以放心做的:换墙纸、刷油漆、换灯具、把沙发从东墙挪到西墙。 房子的功能没变——还是能住、水电还是通,但你住得舒服多了。这就是重构。

还有一类改动是绝对要小心的:拆承重墙。 它看起来只是「把一堵碍事的墙打掉」,但承重墙一拆,楼板可能塌。这不是装修,这是结构工程,得报批、得算荷载、得有人负责。

区别在哪里?换墙纸不改变房子的「可观察行为」(能不能住人),拆承重墙会。

福勒说的「行为不变」,就是要求所有重构都停在「换墙纸」这一侧。一旦你开始碰承重墙——也就是让外部能观察到功能变化——那就不再是重构了。


五、为什么会这样?

把福勒的推理整理成链条:

graph TD
    A[重构需要频繁改动代码结构] --> B[频繁改动必须能快速判断有没有改坏]
    B --> C[判断依据只能是外在行为是否一致]
    C --> D[因此必须约定: 行为不变]
    D --> E[行为不变 → 每次改动可验证]
    E --> F[可验证 → 出错能立即定位和回滚]
    F --> G[可回滚 → 才敢继续小步改动]
    G --> A

关系示意图

这条链是一个闭环。「行为不变」不是一个孤立的规矩,而是让整个重构循环能够转起来的轴心。

反过来看,如果去掉 D 这一步会怎样:

所以「行为不变」保护的其实不是功能,而是「改动的可验证性」。


六、举一个现实生活中的例子

看一个典型的「越界」现场。

一段代码本来长这样:

double total = price * count;
if (total > 100) total = total * 0.9;

工程师觉得这段逻辑写在一个大函数里不好,就把它抽成一个函数:

applyDiscount(price, count)

抽出来之后,他顺手发现「满 100 打九折」的边界写得有点怪,于是改成了 >=,觉得「这样更合理」。

问题就出在这顺手一改上:

正确的做法是:这一次只抽函数,一个字符都不改逻辑;想改边界,另起一次改动,单独评审、单独测试。 这就是福勒说的「不改变行为」在日常里最真实的样子。


七、回到书中重新理解

现在再回看福勒的定义,就能读出它的分量了。

他之所以把「不改变外在行为」写进定义的第一句,不是为了限定重构的范围,而是为了让重构可被检验。行为不变,意味着你可以拿同一组测试反复跑:跑之前通过,跑之后还通过,就能高度确信结构改动没有破坏功能。

这也解释了他为什么强调区分意图。行业里后来常引用的「两顶帽子」(这一提法的源头常被归于肯特·贝克)说的正是这件事:一顶是「加功能」,一顶是「重构」,同一时间只能戴一顶。

两顶帽子不能同时戴,本质就是「可观察行为」这条线不能同时跨越。


八、这个观点背后还有什么?

这条铁律背后,有一个容易被忽略的隐含前提:「可观察行为」是可以被定义、被测量的。

也就是说,你得先知道这个系统「对外承诺了什么」——它的接口、它的返回、它的副作用。只有这些边界清楚,你才能判断重构有没有越界。这其实意味着重构天然依赖两样东西:清楚的接口边界,以及能验证这些边界的自动化测试。

另一层更深的意思是:行为不变,是一种「保守主义」。 它承认开发者并不完全理解代码为什么写成这样——那些看似多余的判断,可能正好在处理某个罕见的历史 bug。所以它选择先不改变行为,只让结构更清晰,把「理解」和「改变」分成两步走。

先把代码变得看得懂,再决定要不要改它的行为。 这个顺序,比它看起来重要得多。


九、这个观点有问题吗?

十、今天再看这个观点

(以下为现代延伸,非福勒原文)

今天的工具,把「不改变行为」这件事做得越来越自动化。

现代 IDE 的重命名、抽取方法、移动成员等操作,在大多数情况下能保证只改结构、不改行为;一些代码分析工具甚至能在改动前后自动比对可观察行为。类型系统、契约测试、灰度发布、特性开关,也在从不同角度帮团队守住这条线。

但工具也带来了新的盲区:能被工具保证的,往往只是「语法层面」的行为不变,而语义层面的行为——比如并发时序、外部依赖的调用次数、异常传播路径——仍然要靠人来判断。 换句话说,工具把「手别抖」这件事解决了,但「这条线到底画在哪」仍然是人要负责的问题。

这也是为什么,越是自动化程度高的团队,越需要清楚地知道自己对外的「行为承诺」到底是什么。没有这份清单,再好的工具也保护不了你。


十一、这一篇真正应该记住什么?

  1. 行为不变是重构的验收标准:没有它,「改好了」和「改坏了」就无法区分,重构也就失去了可验证性。

  2. 判断的依据是「可观察行为」:从系统外部看,同样的输入、同样的动作,必须得到同样的结果。内部怎么重排不算数。

  3. 过线了就不是重构:行为变了,那是加功能或修 bug,只是借了重构的名字。区分清楚,才能决定用多少测试、多少评审。

  4. 装修房子的类比:换墙纸可以,拆承重墙不行。所有重构都停在「换墙纸」这一侧。

  5. 它是为了保住「可回滚」:行为不变 → 可验证 → 可回滚 → 才敢频繁小步地改。这条链是整个重构循环的轴心。


十二、留一个问题

到这里,我们接受了一个前提:只要行为不变,就能证明「没改坏」。

但这句话有个藏得很深的依赖——「证明」这件事,究竟靠什么来完成?

如果每次改完,系统能不能跑对,只能靠人工点一遍、看一眼、凭感觉判断,那「行为不变」就只是一句口号,而不是一道真的保险。

下一篇,我们就来看福勒把什么当成了重构的前提:为什么没有测试,就根本谈不上重构?那张安全网到底长什么样?