坏味道识别(上):神秘命名、重复代码、过长函数、过长参数列表、全局数据

各位好,今天我们聊聊代码里的“坏味道”。

说实话,我入行头三年,最怕听到的就是“重构”两个字。总觉得代码能跑就行,干嘛要动它?直到有一次,我接手一个遗留系统,打开一个函数,好家伙,800多行。我盯着屏幕看了半小时,愣是没看懂它在干嘛。那天晚上我就在想——如果代码能说话,它一定在骂人。

后来我慢慢明白,坏味道不是bug,但它比bug更致命。bug让你程序挂掉,坏味道让你的团队慢慢死掉。今天我们就来识别五种最常见的坏味道。

核心观点:坏味道是代码中潜在的设计问题信号。它们不会立即导致程序崩溃,但会显著降低可维护性和可扩展性。识别坏味道是重构的第一步,也是最关键的一步。

坏味道识别(上)知识体系 五种坏味道 神秘命名 变量/函数/类命名模糊 无法从名称理解意图 重复代码 相同/相似代码出现多次 修改时容易遗漏 过长函数 函数超过20-30行 做太多事情,难以测试 过长参数列表 参数超过3-4个 调用者负担重 全局数据 随处可修改的共享状态 副作用难以追踪 重构目标 可读性 ↑ 可维护性 ↑ 耦合度 ↓ 复杂度 ↓ 五种坏味道相互关联,识别一种往往能发现其他几种

1. 神秘命名

神秘命名,说白了就是“看不懂”。变量叫 abdata,函数叫 doSomethingprocess。你想想看,这样的代码过两周你自己都看不懂,更别说别人了。

我个人习惯是:命名要能表达“为什么”和“做什么”,而不是“是什么”。比如:

// 坏味道:神秘命名
int d; // 这是啥?天数?折扣?
void process(); // 处理什么?怎么处理?

// 好味道:清晰命名
int discountRate;
void calculateOrderDiscount();

我在项目中遇到过最离谱的命名是一个叫 xxx 的类。问原作者,他说“当时随便起的,后来忘了改”。嗯,这个类后来被重构了三次,每次都要花半小时猜它的用途。

命名检查清单:

  • 变量名能否让读者立即知道它的用途?
  • 函数名是否描述了它做的事情?
  • 类名是否反映了它的职责?
  • 命名是否遵循团队约定(驼峰、下划线等)?

2. 重复代码

重复代码是坏味道里的“老油条”。它出现在各种地方:同一个类里的两个方法有相似逻辑、两个子类做类似的事情、甚至不同模块之间复制粘贴。

为什么会这样?说白了就是“复制粘贴一时爽,维护起来火葬场”。

// 坏味道:重复代码
public double calculateEmployeeBonus(Employee emp) {
    double base = emp.getSalary() * 0.1;
    double extra = emp.getYears() > 5 ? 1000 : 0;
    return base + extra;
}

public double calculateManagerBonus(Manager mgr) {
    double base = mgr.getSalary() * 0.1;
    double extra = mgr.getYears() > 5 ? 1000 : 0;
    double mgrExtra = mgr.getTeamSize() * 200;
    return base + extra + mgrExtra;
}

// 重构后:提取公共方法
public double calculateBaseBonus(Employee emp) {
    double base = emp.getSalary() * 0.1;
    double extra = emp.getYears() > 5 ? 1000 : 0;
    return base + extra;
}

public double calculateManagerBonus(Manager mgr) {
    return calculateBaseBonus(mgr) + mgr.getTeamSize() * 200;
}

我曾经在一个项目里发现同一个SQL查询被复制了7次,分散在5个不同的文件里。后来业务改了查询条件,我们改了3个文件,漏了4个。嗯,线上出了bug才发现的。

避坑指南:我曾经以为“就复制一次没关系”,结果那次复制变成了一个持续3年的技术债。记住:第一次复制是偷懒,第二次复制是习惯,第三次复制就是灾难。

3. 过长函数

过长函数,我个人觉得是代码里最直观的坏味道。一个函数超过20行,你就该警惕了。超过50行,基本可以断定它做了太多事情。

你想想看,一个函数如果做了A、B、C三件事,你改A的时候会不会担心影响B和C?测试的时候要不要把所有路径都测一遍?

// 坏味道:过长函数(伪代码)
public void processOrder(Order order) {
    // 1. 验证订单(20行)
    // 2. 计算价格(30行)
    // 3. 检查库存(15行)
    // 4. 更新数据库(25行)
    // 5. 发送通知(10行)
    // 总共:100行
}

// 重构后:每个函数只做一件事
public void processOrder(Order order) {
    validateOrder(order);
    calculatePrice(order);
    checkInventory(order);
    updateDatabase(order);
    sendNotification(order);
}

我记得有一次重构一个300行的函数,拆成8个小函数后,发现其中2个函数根本不需要——因为逻辑是重复的。你看,拆开之后问题自然就暴露了。

经验法则:如果一个函数你需要滚动屏幕才能看完,那它一定太长了。如果一个函数你需要写注释来解释它在做什么,那它应该被拆分了。

4. 过长参数列表

参数列表太长,调用者会崩溃。3个参数是极限,4个就该考虑重构了。超过5个?嗯,基本可以断定设计有问题。

// 坏味道:过长参数列表
public void createUser(String name, int age, String email, 
                       String phone, String address, String city, 
                       String country, String zipCode, boolean isActive) {
    // ...
}

// 重构后:引入参数对象
public class UserInfo {
    private String name;
    private int age;
    private String email;
    private ContactInfo contact;
    private boolean isActive;
    // getters/setters...
}

public void createUser(UserInfo userInfo) {
    // ...
}

我在项目中遇到过最夸张的一个函数有12个参数。调用的时候要按顺序传,传错一个就出bug。后来我们引入了参数对象,代码瞬间清爽了。

参数过多的常见解决方案:

  • 引入参数对象:将相关参数封装成一个类
  • 拆分为多个函数:不同场景用不同函数
  • 使用建造者模式:当参数有大量可选值时

5. 全局数据

全局数据,说白了就是“谁都能改,谁都管不了”。全局变量、单例、静态类变量,这些都是全局数据的变种。

为什么它是个坏味道?因为副作用太难追踪了。你改了一个全局变量,可能影响10个不同的地方。而且这些影响往往是在运行时才暴露的,编译期根本发现不了。

// 坏味道:全局数据
public class Config {
    public static String databaseUrl;
    public static int maxConnections;
    public static boolean debugMode;
}

// 某处代码
Config.databaseUrl = "new_url"; // 谁改的?为什么改?什么时候改的?

// 重构后:封装访问
public class Config {
    private static String databaseUrl;
    private static int maxConnections;
    private static boolean debugMode;
    
    public static String getDatabaseUrl() { return databaseUrl; }
    public static void setDatabaseUrl(String url) {
        // 可以加日志、校验、通知等
        databaseUrl = url;
    }
    // ...
}

我曾经在一个项目里排查一个诡异的bug,查了整整两天。最后发现是某个模块在初始化时偷偷改了全局的数据库连接池配置。嗯,从那以后,我对全局数据就特别敏感。

避坑指南:我曾经以为“就一个全局变量,没事的”。结果这个“没事的”全局变量,后来被5个不同的模块读写,每次上线都要祈祷不要出问题。记住:全局数据是隐形的耦合,它让代码的依赖关系变得不可见。

小结

今天聊了五种坏味道:神秘命名、重复代码、过长函数、过长参数列表、全局数据。它们就像代码里的“小毛病”,单个看都不致命,但积累起来就能让一个项目瘫痪。

识别坏味道是重构的第一步。你不需要一次性把所有坏味道都消灭,但至少要能“看见”它们。下次你打开一个文件,看到模糊的命名、重复的逻辑、超长的函数,心里就要有个声音在说:“嗯,这里该重构了。”

记住一句话:代码是写给人看的,顺便给机器执行。如果人看不懂,那机器执行得再好也没用。


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