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