2. 代码坏味道识别:过长函数、过大的类、重复代码、过长参数列表
好,咱们直接进入正题。
代码坏味道,这个词我第一次听到是在读《重构》那本书的时候。当时觉得挺玄乎,后来在项目里踩了坑才明白——坏味道就是代码在告诉你:我快撑不住了,赶紧改改我吧。
这一章,我挑四个最常见的坏味道来讲。它们就像感冒发烧,几乎每个项目里都能碰到。你如果能提前识别出来,重构就成功了一半。
2.1 过长函数:一个函数超过20行就要警惕
我个人习惯是:一个函数尽量控制在10到15行以内。超过20行,我就开始琢磨怎么拆了。
为什么?因为人脑的工作记忆有限。你想想看,一个函数里塞了五六十行代码,变量满天飞,逻辑绕来绕去——别说别人了,你自己过两周回来看都得懵。
- 函数名已经无法概括它做的事(比如叫
processData但里面既做校验又做转换还写日志) - 函数内部出现多个缩进层级(if 套 if 再套 for)
- 需要滚动屏幕才能看完整个函数
我在项目中遇到过一段代码,一个函数写了快200行。里面既有业务逻辑,又有数据转换,还夹杂着异常处理。后来我把它拆成了7个小函数,每个函数只做一件事。测试也好写了,改起来也放心了。
validateAndSave,那大概率该拆了。
2.2 过大的类:一个类超过200行就要小心
类太大,说白了就是职责太多。一个类既管用户登录,又管订单处理,还管数据导出——这哪是类啊,这分明是个大杂烩。
我一般用这个标准:一个类超过200行,我就开始审视它的职责是否单一。超过500行,基本可以确定需要拆分了。
| 类的大小 | 建议 | 典型问题 |
|---|---|---|
| < 100 行 | 正常 | — |
| 100 - 200 行 | 留意 | 可能隐含多个职责 |
| 200 - 500 行 | 建议拆分 | 难以测试、难以复用 |
| > 500 行 | 必须拆分 | 牵一发而动全身 |
识别过大类还有个技巧:看这个类的字段数量。如果一个类有十几个字段,而且这些字段之间没有强关联,那它很可能在偷偷干好几份活。
2.3 重复代码:复制粘贴一时爽,维护起来火葬场
重复代码,这应该是大家最熟悉的坏味道了。你想想看,同样的逻辑在三个地方出现——改一个地方,另外两个忘了改,bug就来了。
重复代码有几种常见形态:
- 完全相同的代码块:直接复制粘贴,连注释都没改
- 结构相同、细节不同:比如两个函数逻辑一样,只是参数不同
- 逻辑相似但表达不同:比如一个用 for 循环,一个用 while,但做的事一样
怎么处理?我的经验是:第一次出现,正常写;第二次出现,考虑提取;第三次出现,必须提取。
// 重复代码示例
function calculateDiscountVIP(price) {
let discount = price * 0.2;
if (discount > 100) discount = 100;
return price - discount;
}
function calculateDiscountNormal(price) {
let discount = price * 0.1;
if (discount > 50) discount = 50;
return price - discount;
}
// 重构后:提取公共逻辑
function calculateDiscount(price, rate, maxDiscount) {
let discount = price * rate;
if (discount > maxDiscount) discount = maxDiscount;
return price - discount;
}
你看,提取之后,代码量少了,逻辑也清晰了。以后改折扣规则,只需要改一个地方。
2.4 过长参数列表:超过3个参数就该考虑封装了
我有个简单的规则:参数超过3个,就要考虑用对象来封装。
为什么?因为参数越多,调用方越容易搞错顺序。你想想看,一个函数有7个参数,调用的时候得一个一个对着看——这不出错才怪。
- 调用函数时需要翻看文档才能知道参数顺序
- 多个参数经常一起出现(比如
startDate和endDate) - 参数中有很多可选值(比如传
null或undefined)
我在项目中遇到过这样一个函数:
// 坏味道:7个参数,调用方苦不堪言
function createOrder(userId, productId, quantity, price, discount, shippingAddress, paymentMethod) {
// ...
}
// 重构后:用参数对象封装
function createOrder(orderData) {
// orderData.userId, orderData.productId ...
}
// 调用方也清爽了
createOrder({
userId: 123,
productId: 456,
quantity: 2,
price: 99.9,
discount: 0.1,
shippingAddress: "北京市...",
paymentMethod: "wechat"
});
改成参数对象之后,调用方不用记顺序了,而且新增参数也不会影响已有调用。嗯,这里要注意:如果参数对象里的字段经常一起变化,那可能意味着这些字段应该属于一个独立的类。
2.5 四种坏味道的关系图
这四种坏味道其实经常一起出现。过大的类里往往藏着过长函数,过长函数里又经常有重复代码和过长参数列表。它们就像连锁反应,一个坏味道会催生另一个。
2.6 小结:坏味道是重构的信号灯
识别坏味道,说白了就是培养一种"代码嗅觉"。你看到一段代码,觉得"这里不对劲"——那就对了,你的直觉在提醒你。
我个人建议:每次提交代码前,花5分钟审视一下自己的代码。有没有过长函数?有没有重复代码?参数是不是太多了?这5分钟,往往能省下后面几小时的调试时间。
下一章,咱们聊聊怎么用"提取方法"这个最基础也最实用的重构手法,来干掉这些坏味道。