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 行 必须拆分 牵一发而动全身

识别过大类还有个技巧:看这个类的字段数量。如果一个类有十几个字段,而且这些字段之间没有强关联,那它很可能在偷偷干好几份活。

我曾经踩过的坑: 接手过一个"上帝类",里面塞了3000多行代码。改一个功能要翻半天代码,还经常改出bug。后来花了整整两周才把它拆成十几个小类。从那以后,我对"类太大"这件事特别敏感。

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 四种坏味道的关系图

这四种坏味道其实经常一起出现。过大的类里往往藏着过长函数,过长函数里又经常有重复代码和过长参数列表。它们就像连锁反应,一个坏味道会催生另一个。

代码坏味道 连锁反应 过长函数 > 20行需警惕 过大的类 > 200行需审视 重复代码 复制粘贴的代价 过长参数列表 > 3个需封装 相互影响 相互影响

2.6 小结:坏味道是重构的信号灯

识别坏味道,说白了就是培养一种"代码嗅觉"。你看到一段代码,觉得"这里不对劲"——那就对了,你的直觉在提醒你。

我个人建议:每次提交代码前,花5分钟审视一下自己的代码。有没有过长函数?有没有重复代码?参数是不是太多了?这5分钟,往往能省下后面几小时的调试时间。

一句话总结: 坏味道不是bug,但它是bug的温床。早发现、早重构,比等到代码烂成一锅粥再动手要轻松得多。

下一章,咱们聊聊怎么用"提取方法"这个最基础也最实用的重构手法,来干掉这些坏味道。