代码是写给两个人看的:机器(编译器,它不在乎)和六个月后回来的你(它非常在乎)。本章讲代码质量的操作性定义(坏味道清单)、重构的纪律(小步 + 测试保护网)、以及命名与函数这两项日常功。重构(Refactoring,Fowler)的定义先立住:在不改变可观察行为的前提下,改善代码的内部结构。「不改行为」是红线,「内部结构」是全部内容。
坏味道(code smell)是「该重构了」的嗅觉信号,不是错误(编译器全放行),但每一条都在积累未来的成本:
| 坏味道 | 症状 | 常用药方 |
|---|---|---|
| 重复代码 | 同样逻辑出现 ≥ 2 处,改一处漏一处 | 提取函数/类(第 3 条 DRY) |
| 过长函数 | 一屏装不下,注释分隔段落 | 提取函数(按「一个抽象层级」切) |
| 巨型类/上帝类 | 一个类 2000 行、管 5 件事 | 按职责拆(单一职责,第 5 章 SRP) |
| 过长参数列表 | 8 个参数,调用处靠注释对位 | 引入参数对象 |
| 发散式变化 | 一个需求改动横跨 5 个文件 | 按变化方向重新切模块(第 4 章) |
| 注释解释代码 | 代码要靠注释才看得懂 | 改代码,注释只留「为什么」 |
| 霰弹式修改 | 一个小改动牵出 20 处小改 | 内聚化(发散式变化的反面) |
坏味道清单的正确用法:不是背下来,而是在 code review 和写代码时的「膈应感」上找到学术名字,然后查药方。Fowler 的《重构》第二版有完整目录,把其中「长函数」和「重复代码」两条治好,代码质量已能超过多数课程项目。
重构是把手术,两条纪律缺一不可:
小步。每次重构一个动作(提取函数、改名、搬移方法),动作之间跑测试、提交。大重构(一次性改 2000 行再测)出问题时,你不知道哪一步引入的。「小步快跑」在重构语境的含义:每一步都可回滚。
保护网。重构前先有测试(第 8 章),且测试针对行为而不是实现(测「存款 100 后余额为 100」,不测「调用了 save 方法」)。行为测试在重构中必须全绿;测试红了说明你改了行为,要么改错了,要么那是「功能修改」不是重构。没有测试的重构是闭眼开车,先补测试(哪怕是粗粒度的特征测试)再动刀。
IDE(IDEA/VSCode)的重构功能(重命名、提取函数、内联、移动)值得全学会:它们做的是语法级安全的变换,比手改可靠一个量级。
命名是代码可读性的第一杠杆。几个硬规则:
命名的终极检验:给同事(或三个月后的自己)看函数名,他能猜出大致行为,就及格了。「程序员最难的两件事:缓存失效和命名」这个段子,一半是真的。
好的函数有一条可操作的判据:函数体内的语句都在同一个抽象层级(先讲「读文件、解析、过滤」,再在某层展开「解析」怎么做)。混合层级(一行调高级 API、下一行手写字节操作)是可读性杀手。配套规则:函数短(一屏内,可能十几行)、参数少(三个以上考虑参数对象)、无副作用默认(改外部状态要显式)。
还有「early return」风格:
// 嵌套地狱版
if (user != null) { if (user.isActive()) { if (user.hasPermission()) { ... } } }
// 卫语句版
if (user == null) return;
if (!user.isActive()) return;
if (!user.hasPermission()) return;
...主逻辑不缩进
卫语句(guard clause)先排除异常,主路径保持平铺,缩进层级是复杂度的直接读数。
技术债(technical debt,Cunningham 的比喻)是「为了短期交付选择次优实现,欠下的未来成本」。债务不是罪(赶 deadline 的合理杠杆),失控才是。管理它的姿势: