重构基础手法:提炼函数、内联函数、提炼变量、内联变量、改变函数声明

各位同学,今天我们来聊聊重构中最基础、也最常用的几个手法。说实话,这些手法看起来简单,但我在项目中见过太多人用错。说白了,重构不是炫技,而是让代码回归它本该有的样子。

我刚开始带团队时,有个同事写了一段300行的函数,逻辑全挤在一起。我问他为什么不拆,他说「拆了反而不好理解」。嗯,这其实是个常见的误区。今天我们就把这五个手法掰开揉碎讲清楚。

提炼函数:把意图从实现中分离出来

提炼函数,是我个人用得最多的重构手法。没有之一。

它的核心思想很简单:当你看到一段代码需要注释才能看懂时,就该把它提炼成一个函数。函数名就是最好的注释。

// 重构前
let total = 0;
for (let i = 0; i < orders.length; i++) {
  if (orders[i].status === 'paid') {
    total += orders[i].amount;
  }
}
console.log('已支付订单总额:' + total);

// 重构后
function calculatePaidTotal(orders) {
  return orders
    .filter(o => o.status === 'paid')
    .reduce((sum, o) => sum + o.amount, 0);
}

const paidTotal = calculatePaidTotal(orders);
console.log('已支付订单总额:' + paidTotal);

核心原则:一个函数只做一件事。如果你无法用一句话说清这个函数在做什么,那就说明它做得太多了。

我在项目中遇到过最典型的例子:一个函数既在解析数据,又在做校验,还在拼装返回结果。拆成三个函数后,每个函数只有10行左右,测试也更好写了。

内联函数:有时候拆得太碎也不是好事

有提炼就有内联。你可能会问:既然提炼这么好,为什么还要内联?

原因很简单:有些函数拆得太碎,反而让逻辑跳来跳去。你想想看,一个函数只有一行代码,调用它还要跳转一次,这值得吗?

// 重构前 - 过度提炼
function getDiscount(price) {
  return applyDiscount(price);
}
function applyDiscount(price) {
  return price * 0.9;
}

// 重构后 - 内联回来
function getDiscount(price) {
  return price * 0.9;
}

我的判断标准:如果函数体比函数名还短,或者函数名并不能比实现本身提供更多信息,那就内联掉它。

我曾经见过一个项目,每个getter都包了一层函数。比如 getName() 里面就一行 return this.name。这种内联掉之后,代码反而更清晰。

提炼变量:给表达式一个名字

复杂表达式是代码可读性的头号杀手。提炼变量就是给这些表达式起个名字,让读者一眼就能看出它的含义。

// 重构前
if (user.age >= 18 && user.country === 'CN' && !user.isBlocked) {
  // ...
}

// 重构后
const isAdult = user.age >= 18;
const isDomestic = user.country === 'CN';
const isActive = !user.isBlocked;

if (isAdult && isDomestic && isActive) {
  // ...
}

注意:提炼变量不是越多越好。如果一个表达式只出现一次,且含义已经很清晰,就没必要提炼。比如 const two = 2 这种就属于过度提炼。

我个人习惯是:当表达式超过三个操作符,或者包含嵌套函数调用时,我就会考虑提炼变量。尤其是那些在多个地方重复出现的「魔法值」或「魔法表达式」。

内联变量:去掉不必要的中间人

内联变量是提炼变量的反向操作。当一个变量名并不能比表达式本身提供更多信息时,就该把它内联掉。

// 重构前
const basePrice = order.basePrice;
const quantity = order.quantity;
const finalPrice = basePrice * quantity;

// 重构后
const finalPrice = order.basePrice * order.quantity;

你可能会觉得这没什么大不了的。但我在代码评审时,经常看到有人给每个属性都定义一个临时变量。代码变得又长又啰嗦。说白了,变量存在的意义是「解释」,不是「搬运」。

改变函数声明:让函数名说人话

这个手法,说白了就是改函数名、改参数列表。听起来简单,但做起来需要勇气。

我见过太多函数名叫 processData、handleClick、doSomething 这种。你想想看,这种名字跟没起有什么区别?

// 重构前
function calc(a, b, c) {
  // 计算订单折扣,a是金额,b是会员等级,c是优惠券
}

// 重构后
function calculateOrderDiscount(amount, memberLevel, coupon) {
  // 现在看名字就知道参数含义了
}

改名原则:好的函数名应该回答三个问题——做什么、输入什么、输出什么。比如 calculateOrderDiscount(amount, memberLevel, coupon) → number。

我曾经在一个遗留系统里,看到一个叫 f 的函数。对,就一个字母。打开一看,里面200行代码,涉及数据库查询、文件读写、邮件发送。改这个名字花了我一下午,因为要搞清楚它到底在做什么。

知识体系总览

下面这张图,是我对这五个手法的理解。它们两两成对,互为逆操作。核心就一句话:让代码的「意图」和「实现」保持合适的距离。

重构基础手法知识体系 代码可读性 提炼函数 内联函数 提炼变量 内联变量 改变函数声明 ⇄ 互为逆操作 ⇄ 互为逆操作 核心目标:让代码意图清晰,实现简洁

实战建议:什么时候用什么手法

说了这么多,你可能想问:我到底该用哪个?

我总结了一个简单的判断流程:

  1. 看到长函数 → 先提炼函数,把不同层次的逻辑拆开
  2. 看到复杂表达式 → 先提炼变量,给表达式起名字
  3. 看到无意义的包装 → 用内联函数或内联变量去掉它
  4. 看到糟糕的命名 → 毫不犹豫地改变函数声明

避坑指南:我曾经在重构时一次性改了十几个函数名,结果其他同事的代码全崩了。后来我学乖了——改函数声明时,先用IDE的「重命名」功能,确保所有调用点同步更新。如果改参数列表,先加新参数,再删旧参数,分两步走。

最后说一句:这些手法不是银弹。它们解决的是「代码可读性」的问题。如果你的代码本身设计就有问题,比如职责混乱、耦合过高,那光靠这些手法是不够的。但话说回来,好的可读性是进一步重构的基础。先把这些基础手法练熟,后面的路就好走了。

可可精品资料库