代码重构:识别坏味道与安全改动的步骤
几乎每个程序员都吐槽过前任留下的代码:”这坨代码谁写的?”——然后自己又留下新的”杰作”。代码质量会随时间自然退化,这种退化在代码里留下的蛛丝马迹,业界有个形象的说法叫”坏味道”(Code Smell)。它由Kent Beck提出,被Martin Fowler在《重构》中发扬光大。坏味道不是错误——程序能跑,测试能过——但它是一种信号,提示你这里的设计正在腐烂。而重构,就是一门在”不改变外部行为”的前提下,把内部结构变干净的技艺。
一、先学会”闻”:常见坏味道清单
最常见的几种坏味道,值得每个程序员刻在脑子里:
- 过长函数:函数超过50行,往往意味着它干了太多事,难以理解、难以测试、难以复用。
- 魔法数字:代码里直接写86400000,没人知道它是毫秒数还是一天的长度,修改时极易遗漏。
- 重复代码:相似逻辑散落多处,改一处忘一处,bug的温床。
- 过深嵌套:if和for嵌套超过三层,逻辑像迷宫,读代码的人随时迷路。
- 过长参数列表:参数超过四个,调用时容易传错顺序,不如封装成对象。
- 上帝类:一个类承担了太多职责,牵一发而动全身,违反单一职责原则。
坏味道还分层次:直观层的(魔法数字、函数过长、命名混乱)可以用ESLint、SonarQube等静态分析工具自动扫描;微观层的(字段设计不合理、函数职责不单一)需要人工审查发现;宏观层的(上帝类、分层混乱)则涉及架构设计。识别能力是重构的第一步——闻不到味,就谈不上治理。
二、安全重构的节奏:小步、有网、随时提交
重构的定义里藏着最关键的限制:不改变外部行为。所以重构不是重写,不是加功能,更不是修bug——它是给代码做”内部整理收纳”。安全重构有四条铁律:第一,先有测试网。没有测试保护的改动叫裸奔,重构前先给目标代码补上关键路径的测试,让每一次小改动都有绿灯兜底。第二,一次只做一种手法。提取函数、重命名、消除魔法数字、以多态取代条件分支——每次只动一种,跑一遍测试,全绿再提交。第三,小步快跑。每次提交的改动量要小到”错了能立刻回退”,千万别攒一个周末憋个大的。第四,把握时机。Fowler的建议是:在添加功能、修复bug、审查代码时顺带重构,而不是专门立项”重构月”——代码烂到必须专门治理时,往往已经晚了。平时遵守”童子军法则”:离开时让代码比你来时更干净一点。
三、一个案例:两天 vs 两周的差距
一个电商团队的下单模块,核心函数三百多行,嵌套五层,优惠相关的魔法数字散落各处。产品提了个”新客立减”的小需求,负责的工程师看着这坨代码,两周没敢动手——他记得半年前有人”推倒重写”过另一个模块,重写了三天,上线当天出了事故,连夜回滚,从此全组闻重构色变。后来换了一种做法:先给下单的主路径补上十几条测试;然后把价格计算部分提取成独立函数;再把各种优惠规则拆成策略对象,魔法数字变成配置项;每完成一步就跑一遍测试,全绿才提交。整个过程用了两天,需求按时上线。更妙的是,之后再加优惠规则,从”改三百行函数”变成了”加一条配置”。同样的代码,小步重构走通了,推倒重写却翻车了——差别就在”步子大小”。
四、常见误区
- 把重构当重写:推倒重来风险极高。当你觉得”重写比重构简单”时,说明你早就该重构了——但此刻最该做的仍是小步重构。
- 没有测试就动手:没有安全网的重构,是把代码质量押在运气上。
- 一次重构太多:改动面越大,出错概率越高,回退越困难。小步提交是安全感的来源。
- 发布前夜重构:版本临近发布时重构,等于给自己找麻烦,任何不稳定都会被放大。
- 为重构而重构:没有坏味道、没有新增需求、生命周期快结束的代码,不值得动。过度设计同样是坏味道。
- 只靠直觉不用工具:圈复杂度、重复率、告警数这些指标,交给静态分析工具持续盯着,比人肉巡检可靠。
五、行动建议
从今天起,把重构变成日常习惯:写代码时遇到魔法数字,立刻提取成命名常量;函数超过50行或嵌套超过三层,当场拆解;每次提交前跑一遍测试,让”全绿”成为肌肉记忆;在CI里接上静态分析门禁,让工具替你盯住新产生的坏味道。记住Fowler那句名言:任何一个傻瓜都能写出计算机能理解的代码,唯有好程序员才能写出人类能理解的代码。