2、代码坏味道(上):神秘命名、重复代码、过长函数、过长参数列表

说实话,我入行头三年,最怕的不是写不出代码,而是回头看自己写的代码。那种感觉就像翻开一本自己写的日记——字迹潦草,逻辑跳跃,连自己都看不懂。后来我才明白,代码里的「坏味道」不是 bug,但它比 bug 更致命。bug 顶多让程序崩溃,坏味道会让团队崩溃。

今天咱们聊聊四种最常见的坏味道。我敢打赌,你现在的项目里至少能找出两三个。

2.1 神秘命名:代码的第一张脸

先问个问题:你见过变量名叫 abtemp 的代码吗?我见过。而且是在一个核心交易系统里。当时我花了整整两天才搞清楚 temp 到底是临时金额还是临时状态。

神秘命名,说白了就是变量、函数、类的名字让人猜不透。你想想看,代码是写给机器执行的,但更是写给人看的。名字都起不明白,后面的逻辑再漂亮也白搭。

核心原则:名字应该回答三个问题——它是什么?它做什么?为什么存在?

举个我踩过的坑。以前写过一个函数:

// 坏味道
function d(a, b) {
  return a * b * 0.8;
}

// 重构后
function calculateDiscountedPrice(originalPrice, discountRate) {
  return originalPrice * discountRate * 0.8;
}

你看,d 是什么?折扣?距离?还是某个人的名字?重构后一目了然。我个人习惯是:变量名用名词,函数名用动词+名词,布尔值用 is/has/can 开头。

小技巧:如果花超过 3 秒想不出名字,说明这段代码可能设计得有问题。先别急着命名,回头看看逻辑。

2.2 重复代码:复制粘贴的代价

「复制粘贴一时爽,重构维护火葬场。」这句话不是我说的,是我一个同事在代码 review 时吼出来的。

重复代码是最容易识别的坏味道。你只要看到两段代码长得像双胞胎,那就是它了。我在项目中遇到过最夸张的一次:一个支付模块里,同样的金额校验逻辑被复制了 7 次。后来需求改了,我们花了三天才把所有地方找全。

重复代码的常见形式有三种:

类型 表现 重构手法
完全相同的代码 一模一样,连注释都复制了 提取函数
结构相似但细节不同 逻辑骨架一样,只是参数或类型不同 模板方法或策略模式
逻辑重复但写法不同 用不同方式实现了同一件事 统一算法,提取公共逻辑

举个例子,你可能会写出这样的代码:

// 坏味道:重复代码
function calculateOrderTotal(items) {
  let total = 0;
  for (let i = 0; i < items.length; i++) {
    total += items[i].price * items[i].quantity;
  }
  return total;
}

function calculateCartTotal(cartItems) {
  let sum = 0;
  for (let j = 0; j < cartItems.length; j++) {
    sum += cartItems[j].price * cartItems[j].quantity;
  }
  return sum;
}

// 重构后
function calculateTotal(items) {
  return items.reduce((acc, item) => acc + item.price * item.quantity, 0);
}

嗯,这里要注意:重复代码不一定是肉眼可见的复制粘贴。有时候两个函数做的事情一样,只是变量名不同,那也是重复。我曾经在重构一个报表系统时,发现三个不同的团队各自写了「计算平均值」的函数,实现方式还都不一样。你说这维护起来得多痛苦?

避坑指南:不要为了消除重复而过度抽象。如果两段代码只是「看起来像」,但业务含义完全不同,强行合并反而会引入耦合。我曾经见过一个项目,把所有相似逻辑都塞进一个「万能函数」,结果那个函数有 15 个参数……嗯,这就是下一个坏味道了。

2.3 过长函数:一个函数撑起一片天

你见过一个函数超过 500 行吗?我见过。那是一个订单处理函数,从参数校验到数据库操作到发送邮件到生成报表,全塞在一个函数里。我当时的第一反应是:这哥们是把函数当文档写了吧?

过长函数的判断标准其实很简单:如果你需要滚动屏幕才能看完一个函数,那它大概率太长了。我个人习惯是,一个函数最好能在 20 行以内搞定。超过 30 行,我就开始琢磨怎么拆。

为什么会有人写出长函数?说白了,就是「懒」。懒得想名字,懒得拆逻辑,觉得「反正能跑就行」。但你要知道,长函数最大的问题不是长,而是它承载了太多职责。一个函数做三件事,你改其中一件的时候,另外两件很可能就被带偏了。

看个例子:

// 坏味道:过长函数
function processOrder(order) {
  // 验证订单
  if (!order.id || !order.items || order.items.length === 0) {
    throw new Error('Invalid order');
  }
  for (let item of order.items) {
    if (item.price < 0 || item.quantity < 0) {
      throw new Error('Invalid item');
    }
  }
  
  // 计算总价
  let total = 0;
  for (let item of order.items) {
    total += item.price * item.quantity;
  }
  if (order.coupon) {
    total = total * (1 - order.coupon.discount);
  }
  
  // 保存订单
  database.save(order);
  
  // 发送通知
  email.send(order.user.email, 'Order confirmed');
  
  return total;
}

// 重构后
function processOrder(order) {
  validateOrder(order);
  const total = calculateTotal(order);
  saveOrder(order);
  sendConfirmation(order);
  return total;
}

function validateOrder(order) { /* ... */ }
function calculateTotal(order) { /* ... */ }
function saveOrder(order) { /* ... */ }
function sendConfirmation(order) { /* ... */ }

你看,重构后的每个函数都只做一件事。名字就是它的说明书。以后改逻辑,只需要找到对应的函数就行,不用担心牵一发动全身。

拆函数的原则:如果你要给一段代码写注释,那就把它拆成一个函数。注释的内容就是函数名,注释的细节就是函数体。

2.4 过长参数列表:函数的「行李超重」

参数列表太长,说白了就是函数要的东西太多了。我见过最夸张的一个函数有 12 个参数,调用的时候得排成两行。每次调用都像在填表格——少传一个参数就报错,传错顺序就出 bug。

为什么参数会越来越多?因为需求在变,但设计没跟上。今天加一个折扣参数,明天加一个用户类型参数,后天再加一个渠道来源参数……慢慢地,参数列表就变成了「杂物间」。

过长参数列表的典型症状:

  • 调用时参数顺序容易搞混
  • 大部分调用都传同样的值
  • 参数之间有关联关系(比如 startDateendDate 总是一起出现)

重构手法主要有三种:

  1. 引入参数对象:把一组相关的参数封装成一个类或对象
  2. 保留全局状态:如果参数是配置项,考虑用全局配置或依赖注入
  3. 拆函数:如果参数太多是因为函数做了太多事,那就拆

看个实际例子:

// 坏味道:过长参数列表
function createBooking(customerName, customerEmail, roomType, checkInDate, checkOutDate, 
                        hasBreakfast, hasParking, specialRequests, paymentMethod) {
  // ...
}

// 重构后:引入参数对象
function createBooking(customer, room, stay, services, payment) {
  // ...
}

// 其中 customer 包含 name 和 email
// room 包含 type
// stay 包含 checkInDate 和 checkOutDate
// services 包含 hasBreakfast, hasParking, specialRequests
// payment 包含 method

我在项目中遇到过类似的情况。当时是一个酒店预订系统,createBooking 函数有 11 个参数。每次加新功能都要改这个函数,改完还要改所有调用方。后来我把参数分成了 5 个对象,不仅代码清爽了,加新功能也只需要改对应的对象就行。

避坑指南:引入参数对象时,别把不相关的参数硬塞在一起。比如把「用户邮箱」和「房间类型」放在同一个对象里,那还不如不重构。参数对象应该是有业务含义的「概念聚合」,而不是「参数收纳盒」。

知识体系总览

这四种坏味道其实是相互关联的。神秘命名让代码难懂,重复代码让维护成本飙升,过长函数让逻辑混乱,过长参数列表让调用复杂。它们就像多米诺骨牌——推倒一个,其他的也会跟着倒。

下面这张图帮你理清它们之间的关系:

代码坏味道(上)知识体系 代码坏味道 神秘命名 重复代码 过长函数 过长参数列表 常见影响与后果 可读性差 维护成本高 难以测试 易引入 bug 核心思路:识别坏味道 → 选择重构手法 → 小步迭代验证 不要试图一次性解决所有问题,每次只改一个坏味道

识别坏味道是重构的第一步,也是最关键的一步。你不需要记住所有坏味道的名字,只需要记住一个原则:如果一段代码让你觉得「不舒服」,那它大概率有问题。相信你的直觉,然后动手去改。

好了,这四种坏味道就先聊到这儿。下次当你再看到 tempdatafunc1 这样的名字时,记得停下来想一想——这名字,真的配得上这段代码吗?


公众号:蓝海资料掘金营,微信deep3321