16、重构API(上):将查询函数和修改函数分离、函数参数化、移除标记参数
好,咱们今天聊聊API重构。说实话,API这块儿是代码的“门面”,也是最容易积累坏味道的地方。我见过太多系统,API层乱得像一团麻线,牵一根动全身。
这一章我们聚焦三个非常实用的重构手法:查询与修改分离、函数参数化、移除标记参数。这三个手法看似简单,但用好了,能让你的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 又是啥?
你想想看,三个月后回头看这段代码,你还能记得 true 和 false 分别代表什么吗?反正我是不行。
重构方案很简单:拆成两个明确的函数。
// 重构后:意图一目了然
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