从12岁孩子重构代码说起:代码重构的核心概念与工程实践 📅 发布时间:2026/8/31 6:35:11 👁 浏览次数: 前阵子有个新闻在很多技术群里传开了一个 12 岁的小学生把自己家里人的项目代码“重构”了一遍。评论区里吵得很热闹有人夸孩子有天赋有人说这是乱改代码还有人好奇他到底改了什么。这件事本身细节不多但作为一个常年跟代码打交道的人我更关心的是另一个问题一个 12 岁的孩子“重构代码”这四个字背后究竟是理解了重构的本质还是只是在做表面上的整理抛开新闻本身这篇文章我想借这个热点认真聊一聊“代码重构”这件每个开发者都绕不开的事。我会先从概念辨析开始讲清楚重构、重写、格式化这三者的区别然后结合小学生的行为特点分析他大概率改了哪些内容接着用一个完整的 Python 实战案例演示从糟糕代码到可维护代码的逐步重构过程最后再聊聊重构过程中常见的风险、误区和工程上应该遵循的原则。如果你是刚接触编程的新手这篇文章可以帮助你建立对“重构”的正确认知如果你已经有几年开发经验也希望这篇文章能给你一些关于代码质量和团队协作的启发。1. 重构到底是什么先把这个概念掰开揉碎很多人听到“重构”两个字第一反应是“把代码重写一遍”。这个理解其实是不准确的。1.1 重构Refactoring的定义重构这个词最早是由 Martin Fowler 在他那本经典著作《重构改善既有代码的设计》中系统定义的。简单来说重构就是在不改变代码外部行为的前提下对代码内部结构进行调整让它更容易理解、更容易维护、更容易扩展。这里的两个关键点缺一不可不改变外部行为重构前后程序的输入输出应该保持一致。你不是在修 Bug也不是在加新功能你只是把“写得不好的代码”改成“写得更好的代码”。改善内部结构代码要变得更容易读、更容易改。结构混乱、命名随意、函数冗长、重复代码多这些都是重构要解决的问题。1.2 重构 ≠ 重写这是一个很容易混淆的地方。重构是小步快跑的调整。每次改动都很小改完立刻测试保证功能不被破坏。整个过程就像是给房子重新装修墙还是那面墙承重结构没有变只是把水电管线、墙面、地板重新整理一遍。重写是推倒重来。你觉得旧代码已经烂到没法修了于是决定另起炉灶从零开始写一套新代码。这是拆了房子重新盖风险和成本都非常高。很多开发者在面对一段烂代码的时候第一反应是“这代码太垃圾了我要重写”。但如果这段代码已经在线上跑了很久里面包含了很多隐性的业务逻辑和异常处理那么重写往往会导致大量隐藏问题浮出水面。相比之下逐步重构要安全得多。1.3 重构 ≠ 格式化还有一部分人会把“重构”理解成“把代码排版弄整齐”。比如把缩进调对、把空格换成 Tab、给代码加一些注释。这种操作叫格式化Formatting它只是重构里非常非常小的一部分甚至严格来说都不算重构。格式化的确能提升代码的可读性但它不改变代码的结构和逻辑。真正的重构是对代码的结构、命名、职责划分、模块边界等进行调整。你格式化了十段代码代码该烂还是烂你重构了十段代码代码的复杂度是真的下降了。1.4 为什么重构如此重要代码和写文章很像。写第一稿的时候能把思路完整表达出来就已经不错了当你回过头去看会发现很多地方措辞不准确、逻辑不顺畅、前后重复。代码也是这样第一版代码的核心目标是“跑通”但“跑通”和“好维护”是两件事。随着业务迭代代码会变得越来越复杂。如果一开始就注重复构和代码质量后续的每次改动都会更轻松如果放任不管代码会逐渐变成一座“屎山”最后没有人敢动也没有人能理解。小学生改代码这件事之所以能引发讨论恰恰因为“重构”是每个程序员迟早都要面对的重要课题。2. 从新闻事件看一个 12 岁孩子重构代码究竟改了啥新闻里并没有披露具体的代码内容但我们可以从“12 岁小学生”这个身份特征出发合理推测他可能改了什么以及这些改动在程序员眼里算什么水平。2.1 大概率改的是命名和格式一个学编程没多久的孩子最先接触到的“代码整洁”概念通常是变量命名规范和代码格式规范。比如把a、b、tmp这种无意义变量改成userName、orderCount这种有含义的命名。把一长串暴力缩进的代码重新排好让结构清晰起来。把魔法数字直接写在代码里的数字常量提取成有名字的常量。这些改动虽然看起来很简单但对于维护者来说价值其实不小。你看到if (a 1)和看到if (userType USER_TYPE_ADMIN)理解成本是完全不同的。2.2 可能是把重复代码提取成了函数如果这个孩子接受过稍微系统一点的培训他可能会做一件事把重复出现的代码片段提取成函数。举个例子前端页面里经常会有好几处一模一样的事件处理逻辑孩子把它们抽取成一个公共函数然后在多个地方调用。这个水平就比单纯的改命名要高一些了说明他理解了“复用”的价值。2.3 不太可能做的是架构层面的重构但有一点要泼冷水**一个 12 岁的孩子几乎不可能做大范围的架构级重构。**比如把一个单体应用拆分成微服务、把项目从旧框架迁移到新框架、重新设计数据库表结构等。这些事情需要大量的业务理解和技术积累即便是工作多年的高级工程师也需要谨慎评估才能动手。所以新闻里的“重构”大概率是一次“代码清理Code Cleanup”偏向于可读性的改善而不是真正意义上的大型重构。2.4 核心问题孩子到底需要什么比起“他改了什么”我更关注另一个问题为什么一个 12 岁的孩子会去重构别人的代码合理的解释是他在学习编程的过程中被引导着阅读了真实项目代码发现了一些他认为“不够好”的地方然后尝试动手改进。这种好奇心和动手能力是非常宝贵的。对一个未成年人来说能够读懂一段代码、发现问题并尝试修改说明他已经迈过了“只会照着教程写”的阶段开始理解代码是给人看的而不仅仅是给机器运行的。真正的重点不是评判这个孩子重构得好不好而是我们能否从这个事件里学到一些关于代码质量和工程规范的东西。3. 重构之前先学会识别坏代码的几种典型味道在你动手重构任何代码之前你得先能看出这段代码“哪里不好”。在软件开发领域我们把那些“看起来不对劲、可能有问题”的代码称为坏味道Code Smell。下面这几种坏味道是最常见的也是重构最常针对的目标。3.1 重复代码Duplicated Code同样的代码逻辑出现在多个地方。一旦需求变化你需要改好几个地方漏改一处就会出 Bug。最典型的例子是项目里有多个业务模块都需要做“时间格式化”于是每个模块里都 Copy 了一份相同的日期处理代码。3.2 长函数Long Method函数体过长动辄几百行甚至上千行。一个函数里做了太多事情读起来非常吃力。长函数的本质是职责不单一它把输入校验、业务处理、数据组装、日志输出等所有逻辑全部揉在了一起。3.3 过大的类Large Class一个类承担了太多职责字段非常多方法非常多。这种类通常被称为“上帝类”后期极难维护。3.4 散弹式修改Shotgun Surgery一个小的需求变化需要修改多个类或函数。比如“修改用户昵称显示规则”你得去改数据库实体、改业务逻辑、改前端返回、改消息通知……改动点非常分散。3.5 依恋情结Feature Envy某个函数对另一个类的数据和方法表现出过多的“兴趣”。典型表现是在一个类的方法里大量调用另一个类的 Getter/Setter 来操作对方的数据而不是把操作数据的逻辑放在数据所属的类里。3.6 基本类型偏执Primitive Obsession大量使用字符串、整数、布尔值等基本类型来表示业务概念。比如用字符串表示状态、用整数表示金额、用布尔值区分用户类型。短期写着省事长期来看语义模糊且容易出错。3.7 注释掉的代码Commented-Out Code一大段代码被注释掉既没有删除也没有说明为什么保留。它们会让阅读者产生困惑这段代码为什么留下以后还要用吗是有价值的还是过时的识别坏味道是重构的第一步。如果你看一段代码感觉“不太舒服”但说不出哪里不对这些坏味道分类可以帮助你把模糊的感受转化成具体的问题清单。4. 完整实战一个 Python 重构案例从烂代码到可维护代码理论说得再多不如动手改一改。下面这个案例尽量模拟真实项目中常见的状况。我会先给出一段“非常糟糕但能运行”的 Python 代码然后一步步重构它。4.1 项目背景假设我们有一个简单的“订单折扣计算器”根据用户的会员等级和订单金额计算最终应付金额。具体规则如下普通用户满 100 减 10满 200 减 30满 500 减 100。黄金会员在普通用户基础上再打 95 折。铂金会员在普通用户基础上再打 85 折。所有用户都享受“新人立减 20 元”活动仅限首单。最低支付金额不能低于 0 元。其实这个需求本身不算复杂但下面这段代码用了一种非常“崩坏”的写法来实现它。4.2 一段糟糕的“烂代码”# 文件路径refactor_demo/bad_code.py def calc(u, a): # u: 用户信息, a: 订单金额 if a 100: d 0 elif a 200: d 10 elif a 500: d 30 else: d 100 if u[2] 1: d d (a - d) * 5 // 100 elif u[2] 2: d d (a - d) * 15 // 100 if u[3] 1: d d 20 if a - d 0: return 0 return a - d user1 [1, 张三, 2, 0] user2 [2, 李四, 0, 1] print(calc(user1, 300)) print(calc(user2, 50))这段代码有很多问题我们来一一指出函数名calc、参数u、a没有任何可读性。参数u是一个列表里面每个元素的含义完全靠写代码的人自己记。魔法数字到处都是。100、10、200、30、500、100这几个数分别对应什么u[2] 1又是什么意思只有写代码的人自己知道。折扣计算逻辑完全写死没有配置化。没有注释即使有注释也说明不了业务含义。这段代码虽然能运行但它就是典型的“谁的代码谁读得想哭”的代码。4.3 重构第一步建立测试保证行为不变在动手改代码之前最重要的一件事是先写好测试确保重构前后行为完全一致。没有测试做保护的重构就像在悬崖边上走钢丝。下面用 Python 自带的unittest写一个简单测试。# 文件路径refactor_demo/test_bad_code.py import unittest from bad_code import calc class TestCalc(unittest.TestCase): def test_golden_user_300(self): # 黄金会员订单金额 300不是首单 self.assertEqual(calc([1, 张三, 2, 0], 300), 256) def test_first_order_50(self): # 普通用户订单金额 50是首单 self.assertEqual(calc([2, 李四, 0, 1], 50), 30) def test_first_order_10(self): # 普通用户订单金额 10是首单优惠后小于 0 self.assertEqual(calc([3, 王五, 0, 1], 10), 0) def test_normal_user_200(self): # 普通用户订单金额 200不是首单 self.assertEqual(calc([4, 赵六, 0, 0], 200), 170) if __name__ __main__: unittest.main()运行测试确认当前代码的行为已经通过测试作为后面重构的基准。cd refactor_demo python -m unittest test_bad_code -v预期输出test_first_order_10 (test_bad_code.TestCalc.test_first_order_10) ... ok test_first_order_50 (test_bad_code.TestCalc.test_first_order_50) ... ok test_golden_user_300 (test_bad_code.TestCalc.test_golden_user_300) ... ok test_normal_user_200 (test_bad_code.TestCalc.test_normal_user_200) ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK测试全部通过。说明我们对这段烂代码当前行为的理解是准确且覆盖充分的。4.4 重构第二步用类替代无意义的列表原来的u是一个列表里面塞了用户 ID、姓名、会员等级、是否首单四个不同的数据。重构的第一步是把这种无结构的数据替换成有结构、有语义的对象。在 Python 里可以用dataclass来定义数据类既简洁又直观# 文件路径refactor_demo/refactored_step1.py from dataclasses import dataclass dataclass class User: user_id: int name: str member_level: int is_first_order: bool def calc(user: User, order_amount: float) - float: if order_amount 100: discount 0 elif order_amount 200: discount 10 elif order_amount 500: discount 30 else: discount 100 if user.member_level 1: discount discount (order_amount - discount) * 5 // 100 elif user.member_level 2: discount discount (order_amount - discount) * 15 // 100 if user.is_first_order: discount discount 20 final_amount order_amount - discount if final_amount 0: return 0 return final_amount这一步整理后参数结构清晰了很多。calc函数接收一个User对象而不是一个含义不明的列表。但我们仍然在函数内部使用了大量的魔法数字可读性还是不够。4.5 重构第三步用枚举和常量消除魔法数字现在我们要定义会员等级枚举并把折扣规则提取为常量。这样做的好处是业务规则集中管理后续调整时只需要修改一个地方。# 文件路径refactor_demo/refactored_step2.py from dataclasses import dataclass from enum import IntEnum, unique unique class MemberLevel(IntEnum): NORMAL 0 GOLDEN 1 PLATINUM 2 dataclass class User: user_id: int name: str member_level: MemberLevel is_first_order: bool # 满减规则按订单金额区间升序排列 DISCOUNT_RULES [ {threshold: 100, discount: 10}, {threshold: 200, discount: 30}, {threshold: 500, discount: 100}, ] # 会员额外折扣率 MEMBER_EXTRA_DISCOUNT_RATE { MemberLevel.NORMAL: 0.0, MemberLevel.GOLDEN: 0.05, MemberLevel.PLATINUM: 0.15, } NEW_USER_DISCOUNT 20 def _calc_amount_discount(order_amount: float) - float: 根据订单金额计算满减优惠金额。 discount 0 for rule in DISCOUNT_RULES: if order_amount rule[threshold]: discount rule[discount] return discount def _calc_member_discount(user: User, order_amount: float, amount_discount: float) - float: 根据会员等级计算额外折扣金额。 rate MEMBER_EXTRA_DISCOUNT_RATE[user.member_level] # 注意这里优惠基数采用“满减后的金额”业务上更直观 return (order_amount - amount_discount) * rate def calc(user: User, order_amount: float) - float: amount_discount _calc_amount_discount(order_amount) member_discount _calc_member_discount(user, order_amount, amount_discount) total_discount amount_discount member_discount if user.is_first_order: total_discount NEW_USER_DISCOUNT final_amount order_amount - total_discount return max(0.0, final_amount)到这一步代码已经变得很清晰了MemberLevel枚举定义了会员等级不再用0/1/2这种魔法数字。DISCOUNT_RULES把满减规则集中管理方便后续增删。MEMBER_EXTRA_DISCOUNT_RATE把会员折扣率统一映射。函数拆分成了_calc_amount_discount和_calc_member_discount各自职责单一。最后的max(0.0, final_amount)替代了if final_amount 0: return 0的写法语义更简洁。4.6 重构第四步跑测试验证行为是否保持不变这一步很关键。重构不等于返工重写必须时刻验证行为没有变化。我们把测试代码改成针对新代码的版本# 文件路径refactor_demo/test_refactored_step2.py import unittest from refactored_step2 import MemberLevel, User, calc class TestCalcRefactored(unittest.TestCase): def test_golden_user_300(self): user User(user_id1, name张三, member_levelMemberLevel.GOLDEN, is_first_orderFalse) self.assertEqual(calc(user, 300), 256) def test_first_order_50(self): user User(user_id2, name李四, member_levelMemberLevel.NORMAL, is_first_orderTrue) self.assertEqual(calc(user, 50), 30) def test_first_order_10(self): user User(user_id3, name王五, member_levelMemberLevel.NORMAL, is_first_orderTrue) self.assertEqual(calc(user, 10), 0) def test_normal_user_200(self): user User(user_id4, name赵六, member_levelMemberLevel.NORMAL, is_first_orderFalse) self.assertEqual(calc(user, 200), 170) if __name__ __main__: unittest.main()运行新测试python -m unittest test_refactored_step2 -v预期输出和之前完全一致test_first_order_10 (test_refactored_step2.TestCalcRefactored.test_first_order_10) ... ok test_first_order_50 (test_refactored_step2.TestCalcRefactored.test_first_order_50) ... ok test_golden_user_300 (test_refactored_step2.TestCalcRefactored.test_golden_user_300) ... ok test_normal_user_200 (test_refactored_step2.TestCalcRefactored.test_normal_user_200) ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK行为完全保持一致重构成功。对比一下最初的代码和新代码信息密度完全不是一个量级。哪怕过两个月再回头来看也能快速读懂这段代码是做什么的、规则是什么样的、要改哪里。这就是重构最直观的价值它不是为了让代码“看起来更高级”而是让维护代码的人能够更快地理解和修改它。5. 重构中的常见问题与排查思路即使在了解了很多重构原则之后实际操作中还是很容易踩坑。下面列出几个真实项目中高频出现的重构问题以及对应的排查和处理思路。5.1 重构后覆盖率下降问题现象常见原因解决思路重构后代码出 Bug但测试没发现原来的测试覆盖不完整重构后问题暴露先补测试再重构对高风险函数做重点覆盖重构后测试数量减少为了“精简代码”删了一些测试不要删可能带来保护的测试测试也是资产建议重构前先统计核心模块的测试覆盖率。覆盖率太低时至少应该把核心分支的测试补齐再动手重构。5.2 重构范围失控问题现象常见原因解决思路原本只想改一个函数结果改了十几个文件重构时顺手“优化”了很多地方每次重构只围绕一个目标发现其他问题先记录下来另开任务改到一半发现工作量巨大目标范围定得太大小步提交一次重构的分支不要和功能分支混在一起建议严格执行“一次只改一件事”。重构和功能开发不要混在同一个代码合并请求里否则出了问题极难定位。5.3 行为被无意改变问题现象常见原因解决思路重构后个别场景结果不对过度追求“代码优美”而修改了算法或取值逻辑时刻提醒自己外部行为优先级高于代码优美度精度问题浮点数在折扣计算中出现精度丢失涉及金额时推荐用 Decimal 而非 float建议重构时尽量保持原有的计算逻辑顺序不要顺手优化你认为“没问题”的算法。优化是另一个独立任务不在重构范围之内。5.4 同事代码冲突问题现象常见原因解决思路重构分支和他人功能分支冲突重构涉及公共模块多人并行修改提前沟通公共模块重构尽量放在需求空窗期合并时丢失了他人新功能合并冲突解决不当使用 IDE 的合并工具仔细核对每一处冲突建议重构大型模块之前先和团队成员同步计划避免撞车。5.5 重构完还是没法维护问题现象常见原因解决思路代码结构还是混乱只做了重命名和格式化没做真正的结构重构多做函数拆分、类拆分、模块划分多个模块仍然大量重复没有识别重复代码用静态扫描工具检测重复代码系统性消除建议重命名和格式化只是重构的起点。真正要跨过“让孩子清理玩具”和“给房子更换承重结构”的差距需要系统性地学习和练习。6. 工程视角重构应该遵守哪些最佳实践聊完了具体操作和常见问题这一节我们把视野拉高一些谈谈在团队协作中重构应该以什么样的方式开展。我从工程角度总结了几条很实用的最佳实践适用于中小团队和独立开发者。6.1 先建立安全网在任何重构开始之前一定要确保目标代码有足够多的测试。如果你面对的是一段没有测试的代码那么先补测试是你“重构前”的第一个任务。测试是你的安全网。没有安全网任何小幅改动都可能引发未知问题而且你还不知道问题是什么时候被引入的。有了测试你可以频繁运行测试快速验证每一步修改是否正确。如果项目里完全没有测试框架至少也要写一个简单的冒烟脚本手动跑一遍核心流程确保基本功能没有坏。6.2 小步提交频繁验证重构最忌讳“憋大招”。改了一大堆代码然后一次性提交。一旦出了问题你根本说不清是哪一步引起的。正确的做法是每完成一个小的改动就跑一次测试。测试通过后立刻提交一次代码。提交信息清楚说明“改了什么、为什么改”。比如上面实战案例中我先改成dataclass跑一次测试再引入枚举和常量再跑一次测试。每一步都是独立可回滚的。小步重构的另一个好处是代码评审的难度大大降低。同事看到的是一个一个个清晰的提交而不是一个几千行的巨大差异对比。6.3 避免“重构 功能开发”混在一起在真实的项目迭代中经常有人想“我一边重构一边顺手把新功能写了”。这种做法的风险在于如果出了问题你不知道是重构导致的还是新功能导致的。代码评审时评审者很难聚焦既要审查重构逻辑又要审查业务实现。回滚变得更加困难因为两个任务搅在一起。推荐的流程是先在一个独立的分支里完成重构测试通过后合并然后再基于重构后的代码开发新功能。6.4 善用静态分析和工具辅助人工检查代码坏味道是很累的而且容易遗漏。现在有很多静态分析工具可以帮助我们自动发现代码中的问题Python 生态pylint、flake8、mypy。Java 生态Checkstyle、PMD、SpotBugs。前端生态ESLint、Prettier、SonarQube。通用平台SonarQube可以扫描多种语言的重复率、复杂度、潜在缺陷。在项目的持续集成流水线中加入这些工具的检查可以在每次提交后自动输出质量报告。遇到复杂度超过阈值的函数、重复率超过指标的模块优先安排重构。6.5 为重构单独建立任务有人会觉得“重构没有业务价值所以不值得单独建任务”。这个观点是危险的。不重构的代码最终会拖慢整个团队的开发速度。维护烂代码的时间成本远远高于重构的时间成本。建议在任务管理工具中给重构建立单独的任务标明重构的目标模块。重构的原因例如每次加需求都需要改动 5 个文件。预估的影响范围。如何验证依赖哪些测试。有了任务重构才不会变成“抽空做做”的边角料而是有计划、有验证、有结果的技术活动。6.6 重构中要保持“留痕”这里的留痕有两个含义代码提交信息要足够清晰。不要只写“refactor”要写清楚改的是哪个模块、为什么要改。例如refactor(order): 将满减规则提取为配置便于后续调整。关键决策要写入文档或设计记录。如果重构涉及模块边界调整、数据流变化建议在项目文档中补充说明。这样做的好处是几个月后自己或他人回溯这段历史的时候能够快速理解当时的改动意图。7. 重构视角的神奇之处小改动有时能引发大变化回到“12 岁小学生重构代码”这个话题。很多人讨论的重点是“孩子改得好不好”。但如果我们从工程演进的视角看一次小规模的重构往往能产生超出预期的连锁反应。代码结构变清晰了其他人就敢去改它了。一个模块如果命名混乱、逻辑纠缠团队成员就会下意识地绕开它不敢碰它。而一旦有人带头把它整理干净后续的维护者也会更愿意在这个模块里做优化、加测试、补文档。这就像整理房间当你整理好书桌一角之后你会更愿意把整个房间都收拾一遍。所以哪怕这个小学生只是做了一些变量重命名和函数抽取只要他没有破坏原有功能他对这个项目的贡献就已经是正向的了。他证明了一件事代码不是一潭死水它可以通过持续的、小步的修整逐步演化成更健康的状态。这也是我希望每一个开发者都理解的点重构不是大师的专利而是一种任何人都能掌握的习惯。它不需要你有多深的设计功底只需要你先有一颗“不想让代码烂下去”的心然后从改好一个名字、拆出一个函数开始一步一个脚印地走下去。下次当你打开一段代码觉得“这里读起来不舒服”的时候不妨停下来想一想它为什么让你不舒服是命名不清楚是函数太长是重复太多然后花几分钟做一次小幅度的重构再用测试验证一下。你会发现写代码这件事会慢慢从“把它跑通”变成“把它写好”。如果你觉得这篇文章对你有帮助可以收藏备用也欢迎在评论区聊聊你在重构中踩过的坑。