10、搬移特性(上):搬移函数、搬移字段、搬移语句到调用者
重构这件事,说白了就是「把代码放到它该待的地方」。
我经常看到一些项目,一个函数明明只跟 A 模块有关,却偏偏躺在 B 模块里。或者一个字段,大部分时间都在 C 类里被读写,却定义在 D 类中。这种「放错位置」的代码,就像你把钥匙放在冰箱里——不是不能用,但每次找起来都让人抓狂。
这一章我们聊三个最常用的搬移手法:搬移函数、搬移字段、搬移语句到调用者。它们的目标一致:让代码的归属关系更清晰,降低未来的修改成本。
搬移函数:让职责归位
一个函数应该属于它最常使用的那个上下文。如果某个函数大部分时间都在调用另一个类的数据或方法,那它就应该搬过去。
我在项目中遇到过这样一个场景:一个订单处理类里有个计算运费的方法,但它几乎只调用了物流模块的接口。每次改运费规则,都要在两个文件之间来回跳。后来我把它搬到了物流模块,代码反而更清爽了。
搬移函数的判断标准
- 该函数引用了哪个类的成员最多?
- 该函数被哪个类调用得最频繁?
- 搬过去之后,是否减少了跨模块的依赖?
来看一个例子。重构前,Order 类里有一个计算配送时间的函数:
// 重构前:配送时间计算在 Order 类中
public class Order {
private String address;
private double weight;
public double calculateDeliveryTime() {
// 大量依赖物流模块的数据
double baseTime = LogisticsConfig.getBaseTime(address);
double weightFactor = LogisticsConfig.getWeightFactor(weight);
return baseTime * weightFactor;
}
}
这个函数几乎完全依赖 LogisticsConfig 的静态方法。搬移后:
// 重构后:搬移到 LogisticsConfig 中
public class LogisticsConfig {
public static double calculateDeliveryTime(String address, double weight) {
double baseTime = getBaseTime(address);
double weightFactor = getWeightFactor(weight);
return baseTime * weightFactor;
}
}
// Order 中只需简单调用
public class Order {
private String address;
private double weight;
public double getDeliveryTime() {
return LogisticsConfig.calculateDeliveryTime(address, weight);
}
}
你想想看,现在如果物流规则变了,我只需要改 LogisticsConfig 一个地方。这就是搬移函数带来的好处——降低耦合,提升内聚。
我的习惯:搬移函数时,我会先看这个函数里有多少「外部引用」。如果超过一半的引用都不属于当前类,那就果断搬。别犹豫,犹豫就会败北。
搬移字段:数据跟着行为走
字段的归属原则更简单:哪个类最频繁地使用这个字段,它就属于哪个类。
我曾经接手过一个遗留系统,一个 Customer 类里居然有个 shippingAddress 字段。但仔细一看,这个字段只在 ShippingService 中被读写。说白了,它就是个「寄养」在别人家的孩子。
注意:搬移字段时,要小心那些被多个类共享的字段。如果确实需要共享,考虑用「引用对象」或「查询方法」替代直接搬移。
搬移字段的典型场景:
// 重构前:字段放错了位置
public class Employee {
private String name;
private String departmentCode;
private String departmentName; // 这个字段其实属于 Department
}
// 重构后:搬移到 Department 类
public class Department {
private String code;
private String name;
}
public class Employee {
private String name;
private Department department; // 通过关联获取部门信息
}
这样做的好处很明显:部门名称的修改只需要在一个地方进行。如果未来要改部门名称的格式,你不会跑到每个员工对象里去翻。
搬移字段的检查清单
- 该字段是否只在当前类的少数方法中被使用?
- 目标类是否已经包含了该字段的「宿主行为」?
- 搬移后,是否会导致其他类需要频繁跨类访问?
搬移语句到调用者:消除不必要的抽象
有时候,一个函数里包含了太多「调用者专属」的逻辑。这些逻辑只对某一个调用者有意义,对其他调用者来说完全是噪音。
嗯,这里要注意:不要为了「看起来整洁」而把不同场景的代码硬塞进同一个函数。
来看一个例子:
// 重构前:函数里混入了调用者专属逻辑
public void processOrder(Order order) {
// 通用逻辑
validateOrder(order);
calculateTotal(order);
// 这个逻辑只对 VIP 客户有意义
if (order.getCustomer().isVip()) {
applyVipDiscount(order);
sendVipNotification(order);
}
// 通用逻辑
saveOrder(order);
}
如果只有少数调用者需要 VIP 处理,那就应该把它搬出去:
// 重构后:搬移语句到调用者
public void processOrder(Order order) {
validateOrder(order);
calculateTotal(order);
saveOrder(order);
}
// 调用者自己决定是否要加 VIP 逻辑
public void processVipOrder(Order order) {
processOrder(order);
applyVipDiscount(order);
sendVipNotification(order);
}
我个人习惯是:如果一个函数里出现了「if (特定条件)」的代码块,且这个条件只对部分调用者成立,那就考虑搬移。别让通用函数背负太多「特殊使命」。
避坑指南:我曾经把一段日志记录逻辑从通用函数里搬移出去,结果发现三个调用者都写了同样的日志代码。这时候就不该搬移,而是应该把日志逻辑抽象成一个独立的函数,让所有调用者共用。搬移语句到调用者,不是让你复制粘贴,而是让「专属逻辑」回到它该在的地方。
知识体系总览
下面这张图概括了本章三个手法的核心逻辑和适用场景:
小结
这三个手法其实都在回答同一个问题:这段代码到底属于谁?
- 搬移函数:让行为回到它依赖的数据旁边。
- 搬移字段:让数据回到它被使用的地方。
- 搬移语句到调用者:让专属逻辑回到它该在的调用链中。
你想想看,如果每个函数、每个字段、每段逻辑都待在正确的位置,代码读起来是不是就像一本目录清晰的书?重构不是炫技,而是让代码自己会说话。
我的建议:下次你看到一个函数或字段「总觉得哪里不对劲」的时候,别急着改逻辑。先问问自己——它是不是放错了地方?有时候,一次简单的搬移,比改十行代码都管用。
公众号:蓝海资料掘金营,微信 deep3321