“12岁小学生重构代码”这个标题,最近在我信息流里出现了好几次。第一次看到时,我以为是某个培训机构在炒“神童”;点进去看评论,大家争论的其实是另一个问题:这个孩子到底重构了什么?有没有可能只是把一堆代码删掉,然后按自己的喜好重写了一遍?我没有办法核实事件本身,其实也没有必要去核实。因为“12岁”这个标签不管真假,“重构”这两个字都值得认真拆一拆。
拆什么呢?拆三层:第一,重构在工程上到底指什么;第二,一次靠谱的重构该怎么执行;第三,为什么 AI 越来越常用之后,重构反而成了普通开发者的新门槛。如果你也跟我一样,写过几年代码,却始终觉得“重构”是大佬才配做的动作,这篇文章可能会帮你把这件事拉回地面。
1. 为什么“重构”这两个字,比“12岁”更值得较真
先给结论:重构不是返工,也不是重写。它是在不改变外部可观察行为的前提下,通过调整代码内部结构,让后来的维护者更容易读懂、修改和扩展。这个定义听起来很简单,但绝大多数开发者并没有按它执行。
遇到一段乱七八糟的代码,很多人第一反应是“扔掉重写”,这不叫重构。重写是重新实现功能,重构是在现有功能保持不变的情况下做结构整治。更讽刺的是,很多项目里的“重构”最后都变成了重写,然后引入新的 bug,然后没人敢再碰那段代码。如果一个 12 岁孩子能理解“我不动功能,只动结构”,那说明他掌握的是一种比语法更底层的工程思维。
1.1 重构不是返工,而是一种常态
我见过很多团队把重构排成一年一次的专项“大扫除”,平时改需求时却完全不管代码结构。这种做法在心理上把重构变成了一个很沉重的仪式,而不是一个持续发生的常规动作。
实际上,重构更接近写作里的“修改”。写文章时,你不会等整本书稿写完再一次性返工,而是每一段写完后顺手润色。代码也一样。每个需求做完后,把命名改清楚一点,把重复逻辑抽出来,把过深的嵌套拆平一点,这些都是在重构。它不叫“重构专项”,它叫“写完代码后的正常整理”。
如果一个 12 岁孩子被夸“会重构”,恰恰说明他可能没有大型项目的包袱,只是很自然地把代码整理成自己看得懂的样子。这种原始本能,反而比工作多年后“不敢动代码”的心态更值得保留。
1.2 年龄不重要,重要的是有没有“结构感”
这个热点标题最迷惑人的地方,是把年龄当卖点。好像 12 岁会重构就是天才,成年人不会就不如小孩。但仔细想想,重构和年龄没有必然关系。有的人写了十年代码,依然只会复制粘贴;有的人刚开始学函数,就会本能地思考“这一段是不是出现过两次”。
“结构感”是一种识别模式的能力:发现重复、发现命名不符、发现函数太长、发现依赖方向混乱。这种能力可以通过刻意练习获得,和几岁开始学编程没太大关系。
所以我不打算继续追问那个 12 岁小学生到底写了什么。我更愿意把这件事当成一个提醒:如果你看一段代码时觉得“能跑就行”,那么你要补的很可能不是更多语法,而是对结构的敏感度。接下来,我用一段非常简单的示例代码,演示一次完完整整的重构过程。
2. 从一段“小学生级别”的代码,演示重构的三步走
我一直觉得,最好的重构练习对象不是那种几千行的模块,而是一个几行就能写完的小函数。因为重构的所有原则,在小函数里都能看清。下面这段 Python 代码是一个常见的练习例子,我不确定那位 12 岁小朋友是否写过类似的东西,但它很适合用来讨论重构。
def p(c, n, d): t = c * n if n > 10: t = t * 0.8 if d == "VIP": t = t * 0.9 return t这段代码能跑。甚至测试都能过。但如果你把这段代码交给另外一个人维护,他至少要花几十秒去猜:p是什么意思?c是单价还是成本?n是数量还是天数?d是折扣等级还是日期?0.8和0.9是硬编码的折扣率,还是某个业务规则?如果业务规则变了,应该改哪里?
2.1 重构前的坏味道:命名、魔法数字、重复结构
这段代码里有三个非常典型的坏味道。
第一,命名完全不可读。单字母函数名和参数名,除了作者,别人很难理解。代码写出来是给机器执行的,但也是给人读的。如果读的人无法从名字中获取含义,那这行代码的信息传递就已经失败了。
第二,魔法数字直接写在逻辑里。0.8和0.9背后是“大单折扣”和“VIP 折扣”,但代码里根本看不出业务含义。如果哪天折扣系数从 0.8 变成 0.75,你需要在代码里搜索所有0.8,还得判断是价格折扣还是数量系数。这种成本会随着项目变大而急剧膨胀。
第三,两个if分支做的是同一件事——对当前金额乘以一个折扣系数,只是触发条件不同。这种结构如果不加约束,后续每增加一种折扣类型,就要再叠一个if,函数会越来越长。等到某个状态积累到几十行,再想拆就困难得多。
许多人觉得这种代码不算严重,毕竟才几行。但坏味道和行数没有必然关系。一段 5 行的代码,如果每个词都让人猜,维护成本可能比一个 50 行的文档还高。
2.2 动手顺序:先测试、再小改、最后看差异
重构最忌讳“边改边看”。正确顺序是先把原来的行为锁住,再做小步修改,最后对比行为是否一致。
第一步,先保存行为和测试用例。对于上面的例子,可以先用一组输入跑一遍,记下输出。比如这里定义几个用例,分别覆盖普通用户、大单用户、大单 + VIP 用户:
cases = [ (10, 5, "NORMAL", 50), (10, 11, "NORMAL", 88), (10, 11, "VIP", 79.2), ]第二步,开始小步重构。先只做重命名,让函数名和参数名具有业务含义:
def calculate_order_total(unit_price, quantity, customer_level): total = unit_price * quantity if quantity > 10: total = total * 0.8 if customer_level == "VIP": total = total * 0.9 return total这一步没有改变任何逻辑,只是让读者更容易理解。很多人以为重命名不算重构,其实它是最划算的重构之一。因为命名是代码里曝光度最高的信息,一个准确的名字能省掉大量注释和沟通成本。
第三步,把魔法数字提取成常量,把两个折扣分支拆成独立函数:
LARGE_ORDER_DISCOUNT = 0.8 VIP_DISCOUNT = 0.9 def calculate_order_total(unit_price, quantity, customer_level): total = unit_price * quantity total = apply_large_order_discount(total, quantity) total = apply_customer_level_discount(total, customer_level) return total def apply_large_order_discount(amount, quantity): if quantity > 10: return amount * LARGE_ORDER_DISCOUNT return amount def apply_customer_level_discount(amount, customer_level): if customer_level == "VIP": return amount * VIP_DISCOUNT return amount每一步做完,都用最初的cases跑一遍,确认输出没变,再进下一步。不要想着一次到位,重构不是竞赛,每一步都可验证,才叫重构。
2.3 重构后的代码,价值不在“更短”,而在“更好改”
重构后的代码不一定更短,常常还会变长。这是因为提取函数引入了更多结构,但这种“变长”是换取“更好改”的成本。
我们看重构后的版本:新的开发者不需要猜p(c, n, d)是什么意思,从函数名就能知道这是“计算订单总额”。两种折扣规则被拆成独立的函数,以后要调 VIP 折扣系数,直接去改VIP_DISCOUNT常量;要增加“满额减免”的规则,只需要在calculate_order_total里加一行,而不需要动原有的折扣逻辑。
这个例子极其简单,但它完整展示了重构的三个本质动作:改名字、拆函数、消除魔法数字。理解了这三个动作,再去看更大的项目,底层逻辑也是一样的。无非是把“函数”换成“模块”,把“魔法数字”换成“散落的业务常量”,把“重命名”换成“统一术语”。
3. 为什么单次跑通不算重构完成?排查链路不能少
很多人重构完,用几条测试用例跑一下,发现输出一致,就宣布胜利。这在简单函数上或许可行,但在真实项目里,行为一致性远不止“样例输出一致”这一层。
真实重构容易翻车,主要有四类:边界条件、异常输入、外部依赖、性能特征。你样例跑通了,不代表quantity为 0、负数、超大数时也能跑通;不代表输入数据类型变化后不会崩;不代表异步任务、缓存、日志、权限这些周边逻辑没有受影响;更不代表接口响应时间没有从 10 毫秒变成 1000 毫秒。
3.1 你以为行为没变,其实边界已经变了
还用订单折扣这个例子。如果遵循原来的逻辑,quantity > 10时打折。但如果在重构过程中,有人顺手把>改成>=,边界条件就变了:10 件的时候是否打折,结果会不一样。
再比如,如果customer_level传入小写"vip",原代码不会打折;重构后如果为了“更健壮”加了一层lower(),行为也变了。这种“改进”如果不在排查范围里,就会成为一个隐藏 bug。
所以,重构时要先明确驱动行为的规则是什么,而不是“我觉得这里应该更宽松一些”。所有超出原范围的改进,都不应该夹带在重构里。重构只负责结构,不负责顺手改业务。哪怕你发现原代码确实有 bug,也应该先记录下来,另开一个变更去修,而不是混在重构里一起提交。
3.2 一个可复用的重构后检查清单
在实际项目里,我习惯按下面这个顺序排查。它不是万能清单,但覆盖了大多数重构翻车点:
| 检查层级 | 检查内容 | 常见问题 |
|---|---|---|
| 输入层 | 正常值、边界值、空值、类型异常 | 边界符号改动、默认值被替换 |
| 输出层 | 返回值、调用方、序列化格式 | 字段名改了、返回类型变了 |
| 状态层 | 全局变量、缓存、并发访问 | 重复执行产生副作用 |
| 依赖层 | 外部接口、配置项、数据库 | 常量改成配置后 key 对不上 |
| 性能层 | 耗时、内存、慢 SQL | 提取函数后重复计算变多 |
这个清单不需要每次重构都全量执行。普通小函数,重点检查输入和输出;涉及 I/O 的模块,重点检查依赖和状态;核心链路,全部检查。
举个例子,如果重构涉及“提取公共函数”,那么每个原来调用点传入的参数可能都不一样。一个公共函数被五个地方调用,就要覆盖五组参数组合,而不是只测其中一个。这个点经常被忽略。
注意:重构时一旦发现行为不一致,先回退,再分析。继续在错误基础上修改,很容易让“重构”变成“重写”。
3.3 发现行为不一致时,先回退再分析,而不是在错误基础上继续改
这一点非常关键。一旦发现重构后的行为不一致,最好先回到重构前的提交点,重新来一遍,而不是在当前代码里继续修修补补。因为行为不一致意味着你已经偏离了“保持行为不变”的前提,继续修改只会让“重构”变成“重写”。
好的重构节奏是:小步提交,每步可回滚。如果重构依赖 Git,建议每一个语义完整的改动单独提交一次,而不是把所有改动揉成一个大 commit。这样一旦出问题,你可以明确知道是哪一步引入的,回退成本也低。
如果你用 AI 辅助重构,更容易遇到这种“样例跑通但边界变味”的情况。这也是下一节要专门聊的问题。
4. 用 AI 辅助重构,能省时间,但别把判断权交出去
近期看到有团队分享“用 AI 2 天重构 2 万行 Vue 项目”的经验。我没有核实这个数字,也不建议把它当成一个可以直接复制的结论。但它反映了一个趋势:AI 已经能帮开发者做大量重复性的结构整理工作,比如重命名、提取组件、批量改 import、迁移配置等。
这类分享出来后,评论区通常有两种极端反应:一种觉得 AI 要取代架构师了,另一种觉得 AI 重构出来的代码根本没法看。两种都偏了。AI 更适合当“效率工具”,不适合当“架构决策者”。
4.1 AI 重构能做哪些事,不能做哪些事
先说能做:
- 局部重命名和批量替换。
- 提取相似代码块,生成公共函数或公共组件。
- 生成类型注解、接口定义、注释。
- 分析一个模块的依赖关系,辅助你理解代码结构。
- 生成新旧代码的 diff,方便人审阅。
再说不能做:
- 不能理解业务的“语义边界”。它不知道这个模块为什么归属 A 服务而不是 B 服务。
- 不能判断抽象层级的取舍。它可能会抽出很多“过度设计”的函数,让代码更复杂。
- 不能替你做最终验收。它无法确认“行为不变”是不是真的不变,尤其是业务规则隐含在文档和沟通里时。
所以,AI 重构的合理用法是:让 AI 做“初稿”,人做“审阅和决策”。
4.2 一个相对稳妥的 AI 重构流程
如果你也想试试用 AI 辅助重构,我建议按下面这个流程来。
- 选择一个小模块,不要一上来就喂整个项目。这个模块最好是已经有测试覆盖的,或者你可以手工确认行为的。
- 给 AI 明确的约束:保持外部行为不变,只改善可读性或复用性,不要优化业务逻辑。
- 让 AI 输出重构前后的代码 diff,而不是只给你最终代码。
- 把 diff 拿到原有测试用例上跑一遍。
- 自己再走一遍前面的检查清单,重点看边界和依赖。
- 确认无误后,按语义拆成多次提交,而不是一次提交全部。
这里最关键的是第 2 步。很多人让 AI“优化一下代码”,AI 就会自作主张地修改业务规则。你应该说“不要改变任何输入输出和逻辑,只提炼重复部分、改善命名、补充注释”。提示词越具体,AI 越不会自由发挥。
比如,如果输入是上面那个p(c, n, d)函数,一个相对明确的提示词可以是:
这是一个订单总额计算函数。现有逻辑是:单价乘以数量得到小计;数量大于10时打8折;客户等级为VIP时再打9折。请保持这些逻辑和输入输出完全不变,只做以下改进:使用具有业务含义的函数名和变量名,将两个折扣分支拆成独立函数,将魔法数字提取为常量。输出重构前后的代码和差异说明。你看,约束越具体,AI 就越像工具,而不是“替代者”。
注意:AI 重构出来的 diff,凡是看起来像“顺手优化”的地方,先标出来,逐个确认是不是改变了原有逻辑。
4.3 代码评审时,重点关注 AI 喜欢“悄悄改变”的地方
用 AI 重构完,人工评审要特别留意几类变化:
- 条件判断里的边界值,比如
>变成>=。 - 默认值或 fallback 行为被改了。
- 函数被“合并”后,原本独立处理的异常分支消失了。
- 字符串、数字、格式被“统一”后,和外部接口不匹配了。
这些点表面上看起来更“规范”,但在重构语境里都属于行为变化,应该被叫停。我在实际使用中,会把 AI 生成的 diff 里所有看起来“顺手优化”的地方都标出来,逐个确认;凡是和原逻辑不一致的,一律还原。
一句话:AI 能帮你把 90% 的体力活干完,但最后那 10% 的语义确认,必须由人来做。这个比例可能随时变化,但“人做最终判断”这个原则,短时间内不会变。
5. 把热点变成方法:重构项目的五个判断标准
聊到最后,我们来沉淀一个可复用的判断框架。不管你是手工重构,还是让 AI 帮忙,只要重构完成,都可以用这五个标准来验收。
| 判断标准 | 具体问题 | 如果不过关怎么办 |
|---|---|---|
| 行为不变 | 是否有测试用例覆盖关键输入输出? | 先补测试,再继续重构 |
| 范围可控 | 改动是否被限制在一个模块或一个函数内? | 拆小提交,不要一次性铺开 |
| 可回滚 | 是否做到了小步提交,每步可以单独回退? | 用 Git 重新整理提交粒度 |
| 可读性 | 换一个人来看,能否更快理解业务含义? | 重新检查命名和函数拆分 |
| 后续可维护 | 是否消灭了坏味道,而不是引入新抽象? | 对照坏味道清单重新审阅 |
这五个标准不需要你背下来,用的时候问自己一句就够了:改完之后,这段代码是不是更容易改了?