重构:何时重构与如何安全重构

代码能跑,但改不动。 想加一个小需求要动五六处、牵一发动全身;函数几百行、命名看不懂、重复逻辑到处都是——这时候需要的是重构:在不改变「对外行为」的前提下,改善结构、提高可读性和可维护性。本章讲什么是重构坏味道与重构时机小步重构与测试保护常见重构手法(提取方法、重命名、移动),以及重构与新功能的平衡

一、什么是重构

重构(Refactoring)是在不改变软件可观察行为的前提下,改进其内部结构的活动。关键词:行为不变——用户、调用方、测试看到的结果与之前一致;只改结构——命名、拆分、移动、去重复,让代码更易读、易改、易扩展。因此重构既不是「修 bug」也不是「加功能」,而是专门为「把代码收拾好」留出的动作;做完重构再加功能,往往事半功倍。

重构:从混乱到清晰,对外行为保持不变

二、坏味道与重构时机

坏味道(Code Smell)是代码里「可能有问题」的信号,不一定错,但往往提示该看看能不能重构。典型坏味道包括:过长函数/过大类重复代码神秘命名过长参数列发散式修改(改一个需求要动很多类)、依恋情结(函数更关心别的类的数据 than 自己的)、数据泥团(总是一起出现的一坨数据)等。发现坏味道不一定要立刻动手,但在「要在这里加功能」或「这里已经改不动了」时,优先做一轮重构再继续。

过长函数 / 过大类
难以理解、难测,适合拆成小函数或拆类
重复代码
同一逻辑多处出现,改一处易漏,提取公共方法
神秘命名
名字不表意,重命名即可大幅提升可读性
过长参数列
参数一堆难记易错,考虑对象或参数对象
发散式修改
改一个需求动多处,职责可能混在一起
依恋情结
函数总用别的类的数据,考虑移动职责
常见坏味道与对应方向

重构时机:触手可及的是「三次法则」——第一次写就那样、第二次重复时留意、第三次重复时一定要抽;或者「加功能前」——要改的这块若很难下手,先小步重构再加;或者专门留「重构时间」在迭代里,避免技术债只增不减。

三、小步重构与测试保护

重构最怕改坏:行为一变就成 bug。所以小步进行、每步都可验证。每次只做一种、范围小的改动(例如只重命名一个变量、只提取一个方法),改完立刻跑测试或手工验证;通过再下一步。这样若出错能立刻定位到「哪一步」出的问题。测试是重构的安全网:有自动化测试(单元测试、回归测试)时,重构后跑一遍即可确认行为未变;没有测试时,至少要有可重复的验证方式(如主流程走一遍),否则大动干戈容易引入隐性缺陷。

1. 小步改动 2. 运行测试 3. 通过则提交 4. 重复
小步重构循环:改一点 → 测 → 通过再继续

四、常见重构手法:提取方法、重命名、移动

手法很多,这里挑最常用、最安全的几种。

提取方法(Extract Method)
把一段可以命名的代码块提取成独立函数,用函数名表达意图,原处改为调用该函数。长函数变短、逻辑更清晰,且提取出的方法可复用或单测。
例:函数里有一段「计算订单税费」的代码,提取为 calculateOrderTax(items),原处调用它。
重命名(Rename)
变量、函数、类、文件改成更贴切的名字。现代 IDE 支持「重命名」并全局更新引用,风险低、收益高,是最值得优先做的重构之一。
例:dataorderItemsproc()validateAndSaveOrder()
移动(Move Method / Move Class)
把方法或类移到更「属于」它的地方——若方法更常用另一个类的数据,就移到那个类;若类职责更贴近另一模块,就移到那个模块。让「谁管什么」更清晰。
例:订单里的「计算运费」逻辑主要用物流规则,移到 ShippingService

提取前

function processOrder(o) { // ... 校验 let tax = 0; for (const i of o.items) tax += i.price * i.qty * 0.1; o.tax = tax; // ... 保存 }

提取后

function processOrder(o) { validateOrder(o); o.tax = calculateOrderTax(o.items); saveOrder(o); } function calculateOrderTax(items) { return items.reduce((s, i) => s + i.price * i.qty * 0.1, 0); }
提取方法示例:长函数拆成有名字的小函数

五、重构与新功能的平衡

重构不是「有空再做」的附加项,而是「要在这里加功能时,若结构太差就先收拾一下」的必选项。但也要避免两种极端:只加功能不重构,债越堆越多;没完没了重构,需求迟迟不交付。建议:在动到某块代码时顺带做局部重构(如加一个需求时顺便提取方法、重命名),而不是动辄「全盘重写」;把「重构」写进任务或 DoD(Definition of Done),每个迭代留一点时间还技术债;大范围重构要有测试或分阶段、可回滚,避免一次改太多导致难以收尾。

一句话: 重构是在行为不变的前提下改善结构。通过坏味道发现改进点,在加功能前或重复出现时动手;用小步 + 测试保证安全。常用手法有提取方法、重命名、移动。重构与加功能要平衡:动到哪块就顺带收拾哪块,避免只堆功能或只搞大重构。

小贴士: 若当前没有测试,又不得不重构,可以先把「要改的那段」用一段「可重复的手工操作」记下来(例如「打开订单页,提交一笔,看金额对不对」),改完按同样步骤验证一遍,再逐步补自动化测试。

六、小结

重构是不改行为只改结构的活动。坏味道(过长函数、重复、神秘命名等)提示改进时机;在加功能前或「改不动」时优先重构。小步重构 + 测试保证安全;常用手法有提取方法、重命名、移动。重构与需求要平衡,动到哪块顺带收拾,并留时间还技术债。下一章讲单元测试与 TDD,把「测试保护」和红-绿-重构循环说细。