16、重构API(上):将查询函数和修改函数分离、函数参数化、移除标记参数

好,咱们今天聊聊API重构。说实话,API这块儿是代码的“门面”,也是最容易积累坏味道的地方。我见过太多系统,API层乱得像一团麻线,牵一根动全身。

这一章我们聚焦三个非常实用的重构手法:查询与修改分离函数参数化移除标记参数。这三个手法看似简单,但用好了,能让你的API接口清晰一大截。

API重构三大手法 查询与修改分离 函数参数化 移除标记参数 原则:一个函数只做一件事 要么查询状态,要么修改状态 避免副作用隐藏 合并重复逻辑 用参数取代重复函数 减少代码冗余 用明确参数替代布尔值 避免 true/false 魔法值 提升调用可读性 目标:API 清晰、安全、易维护

1. 将查询函数和修改函数分离

这个原则说白了就是:一个函数要么查东西,要么改东西,别混着来。你想想看,如果一个函数既返回了数据,又偷偷改了内部状态,调用方很容易踩坑。

我在项目中遇到过这样一个案例:有个函数叫 getUserAndUpdateLoginTime(),名字就透着一股“坏味道”。调用的人以为只是查用户信息,结果每次调用都更新了登录时间,导致后续的统计逻辑全乱了。

核心原则:任何有副作用的函数,都不应该同时承担查询职责。

来看一个典型的反面例子:

// 坏味道:查询和修改混在一起
public String findUserEmail(String userId) {
    User user = userRepository.findById(userId);
    user.setLastAccessTime(LocalDateTime.now());  // 副作用!
    return user.getEmail();
}

这个函数叫 findUserEmail,听起来就是个查询操作。结果它偷偷改了 lastAccessTime。调用方完全不知情。我曾经因为这种代码排查过一个线上bug,花了整整一个下午才定位到问题——嗯,从那以后我对这种“隐藏副作用”深恶痛绝。

正确的做法是拆成两个函数:

// 重构后:职责清晰
public String findUserEmail(String userId) {
    User user = userRepository.findById(userId);
    return user.getEmail();
}

public void updateLastAccessTime(String userId) {
    User user = userRepository.findById(userId);
    user.setLastAccessTime(LocalDateTime.now());
    userRepository.save(user);
}

小技巧:如果你发现一个函数的名字里同时包含了“get”和“update”这类词,基本就是需要拆分的信号。

2. 函数参数化

这个手法解决的是“重复代码”问题。说白了,如果你发现好几个函数长得几乎一样,只是某个常量不同,那就把那个常量提取成参数。

我举个例子。假设系统里有三个函数,分别计算不同部门的奖金:

// 重复代码:三个函数几乎一样
public double calculateSalesBonus(double baseSalary) {
    return baseSalary * 0.15 + 1000;
}

public double calculateEngineerBonus(double baseSalary) {
    return baseSalary * 0.12 + 800;
}

public double calculateManagerBonus(double baseSalary) {
    return baseSalary * 0.20 + 1500;
}

这三个函数,逻辑结构完全一样,只是比例和固定值不同。这时候就该用参数化:

// 重构后:一个函数搞定
public double calculateBonus(double baseSalary, double rate, double fixedAmount) {
    return baseSalary * rate + fixedAmount;
}

// 调用时传参
double salesBonus = calculateBonus(10000, 0.15, 1000);
double engineerBonus = calculateBonus(10000, 0.12, 800);
double managerBonus = calculateBonus(10000, 0.20, 1500);

我个人习惯是,当发现第三个类似的函数出现时,就立刻做参数化。别等到第十个才动手——那时候改起来就费劲了。

注意:参数化不是越多越好。如果参数超过3个,或者参数之间有关联逻辑,那可能意味着需要引入新的对象或策略模式。

3. 移除标记参数

标记参数,最常见的就是布尔值参数。比如:

// 坏味道:布尔标记参数
public void processOrder(Order order, boolean isUrgent) {
    if (isUrgent) {
        // 加急处理逻辑
        expediteShipping(order);
        sendSmsNotification(order);
    } else {
        // 普通处理逻辑
        normalShipping(order);
        sendEmailNotification(order);
    }
}

调用的时候长这样:

processOrder(order, true);   // 这 true 是啥意思?
processOrder(order, false);  // 这 false 又是啥?

你想想看,三个月后回头看这段代码,你还能记得 truefalse 分别代表什么吗?反正我是不行。

重构方案很简单:拆成两个明确的函数。

// 重构后:意图一目了然
public void processUrgentOrder(Order order) {
    expediteShipping(order);
    sendSmsNotification(order);
}

public void processNormalOrder(Order order) {
    normalShipping(order);
    sendEmailNotification(order);
}

调用的时候:

processUrgentOrder(order);   // 清晰!
processNormalOrder(order);   // 也清晰!

我的经验:如果你在代码里看到类似 doSomething(x, true, false, true) 这样的调用,基本可以断定这里需要重构了。标记参数是代码可读性的头号杀手之一。

当然,有时候标记参数不止布尔值,也可能是枚举或字符串。处理思路是一样的:把不同的行为路径拆成独立的函数

小结一下

这三个手法,其实都指向同一个目标:让API的意图更清晰

  • 查询与修改分离:避免副作用隐藏,让调用方放心使用
  • 函数参数化:消除重复代码,用参数统一相似逻辑
  • 移除标记参数:用明确的函数名替代模糊的布尔值

说实话,这些手法都不难,难的是在日常编码中保持警觉。我每次code review时,都会特别留意这三个坏味道。一旦发现,立刻重构——别等到后面再说,那时候成本就高了。

一个实用建议:写代码的时候,多问问自己:“这个函数的调用者,能不能一眼看懂它在做什么?”如果答案是否定的,那就该重构了。


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