代码质量不是靠感觉
「这段代码写得很乱」是每个人都能说一句的话,但乱在哪、有多乱、该先收拾哪一段,往往说不清楚。凭感觉评判代码,最后只会变成个人偏好之争,解决不了真实的可维护性问题。
代码质量需要一些可观察、可比较的指标,帮你在成千上万行代码里,快速定位到真正危险的那几处。度量不是为了给人打分,而是为了把有限的精力花在刀刃上。
重构最怕的不是改不动,而是不知道该先改哪里。度量就是那盏指路灯。
- 圈复杂度数一个函数有多少条分支岔路。
- 度量用途找重点、看趋势,不当成绩排名。
- 测试先行重构前先把行为用测试钉住。
- 小步手法抽函数、搬字段、改命名,一次只动一处。
- 持续做把重构融进日常需求,而不是搞大扫除。
圈复杂度:数一个函数有多少条岔路
圈复杂度是最常用的一个指标,思路很直接:一个函数里有多少条可以走的路。每多一个 if、循环、case、短路条件,就多一条岔路,测试时要覆盖的情况也就翻一倍。
一个圈复杂度高的函数,意味着读它时要在脑子里同时追踪很多种组合,出 bug 的概率天然更高。它不需要精确,只要能让你一眼看出「这个函数太岔了」。
# 圈复杂度高的样子:一个函数里塞满分支(示意)
def 结算(订单):
if 订单.是会员:
if 订单.有券:
if 订单.满减:
… # 每条 if 都是一条岔路
else:
…
else:
if 订单.有券:
…
# 岔路一多,读代码像在走迷宫,测试也难写全。
经验上,一个函数的圈复杂度明显超过周边同类,它就是你下一个该收拾的对象。
度量是为了找重点,不是为了扣分
一旦把度量变成对人的考核,事情就变味了:大家开始为了数字好看而拆分函数、堆注释,代码反而更难读。度量的正确用法是看趋势和找异常。
| 度量 | 看什么 | 不该怎么用 |
|---|---|---|
| 圈复杂度 | 哪个函数突然高得离谱 | 给每个开发者排名次 |
| 重复率 | 哪几段几乎复制粘贴 | 追求零重复而过度抽象 |
| 函数长度 | 哪个函数长到读不下来 | 强行拆成十几个小函数 |
| 测试覆盖 | 核心逻辑有没有被钉住 | 为凑覆盖率写无意义用例 |
把度量接进 CI,让它在评审时温和地提示哪里变复杂了,而不是作为一个红黑榜。
重构第一步:先补测试保护
没测试就重构,等于蒙着眼睛拆炸弹。你改了结构,很可能悄悄改了行为却不自知。所以重构的第一条纪律是:动手之前,先让现有行为被测试罩住。
- 先跑通一套覆盖这个函数主要路径的测试,让它变绿。
- 之后每次小重构,都跑一次测试,一直是绿的才继续。
- 测试意外变红,说明这步改动碰到了行为,立刻回退或修正。
# 重构节奏(红-绿-重构里的「重构」阶段)
1. 先有一套覆盖旧行为的测试,全部通过
2. 做一个小改动:把一段逻辑抽成函数
3. 跑测试 —— 还是绿的?继续下一个小改动
4. 哪一步红了?回退那一步,想清楚再动
# 有测试托底,重构才敢放心大胆地进行。
如果这段代码实在没有测试、也补不出来,那就先给它补一个最粗糙的冒烟用例,哪怕只是钉住一两个关键行为。
常用重构手法:抽函数、搬字段、改命名
大部分重构不需要什么高深招式,几个朴素手法反复用就够了。关键是一次只用一个、改完跑测试,而不是一次性大改。
| 手法 | 解决什么 | 什么时候用 |
|---|---|---|
| 抽取函数 | 一个函数太长、职责太多 | 发现一段逻辑能独立命名时 |
| 搬移字段/方法 | 数据放错了对象 | 某字段总被另一个对象读来读去 |
| 改好命名 | 名字看不懂、误导人 | 读到一个别扭名字时随手改 |
| 合并重复 | 几段几乎一样的逻辑 | 第三次出现相似代码时 |
# 抽取函数前后(示意,重点在「行为不变」)
# 改前:结算函数里混着算折扣的一大段
def 结算(订单):
算折扣(订单) # 抽出来的小函数
算运费(订单)
return 订单.总价
# 改后:主函数读起来像一个目录,细节藏在小函数里
# 测试全程是绿的 —— 只是把结构摆整齐,行为一点没变。
好名字本身就是最好的注释:当一段逻辑需要注释才能看懂,先试试给它起个好函数名。
在分支里小步重构,别搞大重构
最容易翻车的方式,是攒一个「大重构」分支,几周不合并,把整个模块推倒重写。等到合并那天,冲突和已经变化的需求会把它拖死。
- 重构要混在正常需求里做:这次改某函数时顺手把它抽干净。
- 每次重构都是小改动、小提交,能独立评审、独立回滚。
- 不单独开「重构周」,而是让它成为每次提交的一部分。
这种方式慢,但每一步都安全,代码在不知不觉中变整齐,团队也不会因为一次大改而提心吊胆。
让质量成为习惯而不是运动
代码质量从来不是靠一次专项运动提上来的,而是靠每天顺手收拾一点点。当团队把度量提示、测试保护、小步重构当作写代码的默认动作,系统就不会越写越烂。
- 评审时多看一眼:这段是不是该抽个函数、改个名。
- CI 里的复杂度提示当作参考,不当作拦路虎。
- 新人入职读代码时的吐槽,就是下一个重构点。
说到底,度量和重构都是手段,目的只有一个:让三个月后回来改这段代码的人,不至于骂街。把这件事放进日常,比任何一次大扫除都管用。