10、搬移特性(上):搬移函数、搬移字段、搬移语句到函数内/外
代码重构里,有一类操作特别常见——搬移。
说白了,就是把不合适的东西,挪到它该待的地方去。
我刚开始写代码那几年,经常犯一个毛病:一个函数越写越长,一个类越塞越满。总觉得“先放着吧,以后再说”。结果呢?代码像一团乱麻,改一个地方,牵出一堆问题。
后来我学乖了。发现不对劲,就立刻搬。搬函数、搬字段、搬语句。这些小动作,看着不起眼,但能让代码结构瞬间清爽。
今天我们就聊聊搬移的三种常见场景。
核心原则:每个函数、每个类、每个模块,都应该有清晰的职责边界。当某个东西放错了位置,就搬走它。
搬移函数
先说说搬移函数。这是最常用的一种。
你想想看,一个函数如果大部分时间都在访问另一个类的数据,或者跟另一个模块耦合很深,那它很可能放错了地方。
我在项目中遇到过这样一个例子:一个订单类里,有个计算运费的方法。这个方法里,大量调用了物流模块的接口和配置。每次改物流规则,都得来改订单类。这合理吗?不合理。
正确的做法是:把运费计算函数,搬到物流模块里去。
// 重构前:运费计算函数在订单类里
class Order {
calculateShipping() {
// 大量依赖物流模块的配置和接口
const rate = ShippingConfig.getRate(this.zone);
const weight = this.getTotalWeight();
return rate * weight;
}
}
// 重构后:搬到物流模块
class ShippingService {
calculateShipping(order) {
const rate = this.getRate(order.zone);
const weight = order.getTotalWeight();
return rate * weight;
}
}
搬完之后,订单类不再关心运费怎么算。物流模块自己管自己的逻辑。改动时,影响范围也小了。
判断标准:如果一个函数跟当前类的交互很少,却频繁调用另一个类的成员,那就该搬。
搬移字段
字段搬移,比函数搬移更微妙一些。
字段是数据。数据放错了地方,会导致整个类的职责混乱。
我记得有一次维护一个老系统,一个用户类里居然有个字段叫 lastLoginIp。乍一看没问题,用户登录嘛。但仔细一看,这个字段只在审计日志里用到,跟用户的核心属性(姓名、邮箱、角色)完全无关。
这就是典型的放错位置。应该搬到审计日志类里去。
// 重构前:字段放错了地方
class User {
name: string;
email: string;
lastLoginIp: string; // 这个字段跟用户核心职责无关
}
// 重构后:搬到审计日志类
class AuditLog {
userId: number;
loginIp: string;
loginTime: Date;
}
class User {
name: string;
email: string;
// 不再包含 lastLoginIp
}
搬完之后,用户类更纯粹了。审计日志类也完整了。各司其职。
注意:搬移字段时,要检查所有引用点。别漏了。我曾经漏掉一个序列化配置,结果上线后数据丢了半天的日志。血的教训。
搬移语句到函数内/外
这个操作更细粒度。搬的不是整个函数,而是函数里的某几行语句。
场景有两种:
- 搬进函数:调用函数前后,总有一些重复的准备工作或收尾工作。把这些语句搬进函数内部,减少重复。
- 搬出函数:函数里有些语句,跟函数的核心逻辑无关。搬出去,让函数更聚焦。
举个例子。我见过一段代码,每次调用发送通知函数前,都要先检查用户是否订阅了通知。
// 重构前:每次调用前都要重复检查
if (user.isSubscribed) {
notificationService.send(user, message);
}
// 重构后:把检查搬进函数内部
notificationService.send(user, message);
// 函数内部
class NotificationService {
send(user, message) {
if (!user.isSubscribed) return;
// 实际发送逻辑
}
}
搬进去之后,调用方省心了。函数自己也更健壮了——不会因为调用方忘了检查而出错。
反过来,有时候也需要把语句搬出来。比如一个函数里,既有核心计算逻辑,又有日志记录、性能统计等杂活。这些杂活可以搬出去,让函数只做一件事。
// 重构前:函数里混了日志和统计
function processOrder(order) {
console.log('开始处理订单', order.id);
const start = Date.now();
// ... 核心处理逻辑 ...
console.log('处理完成,耗时', Date.now() - start);
}
// 重构后:搬出日志和统计
function processOrder(order) {
// 只保留核心逻辑
// ... 核心处理逻辑 ...
}
// 日志和统计由调用方或装饰器处理
我的习惯:写函数时,先写核心逻辑。写完后再看,哪些语句跟核心逻辑不在一个抽象层次上,就搬出去。这样函数读起来像一篇好文章,层层递进。
知识体系总览
下面这张图,把搬移特性的三种场景串起来了。你可以看到,搬移的核心是职责对齐。
避坑指南
搬移操作看着简单,但坑不少。我踩过几个,分享给你。
- 搬移前,先确认没有副作用。函数搬走后,原来的调用方还能不能正常工作?字段搬走后,序列化、持久化有没有影响?
- 搬移语句时,注意执行顺序。有些语句看似无关,但可能隐含着前置条件。搬出去后,顺序变了,逻辑就错了。
- 不要一次搬太多。我见过有人一口气搬了十几个函数,结果改完一运行,全崩了。小步快跑,搬一个测一个。
我曾经犯过一个错:搬移字段时,只改了业务代码,忘了改数据库迁移脚本。结果上线后,新数据写到了新字段,老数据还在旧字段里。查询时两边对不上,排查了半天。所以,搬字段一定要同步检查数据存储层。
小结
搬移函数、搬移字段、搬移语句,这三招是重构的基本功。
它们不改变代码的行为,只改变代码的位置。但位置对了,代码的 readability 和 maintainability 会大幅提升。
我个人习惯是:每次 review 代码时,看到放错位置的东西,就顺手搬一下。别等。越等越乱。
嗯,今天就聊到这儿。下一节我们继续聊搬移特性的另外几种场景。
公众号:蓝海资料掘金营,微信 deep3321