基本重构手法(一):提炼函数、内联函数、提炼变量、内联变量
重构这件事,说白了就是「整理代码」。就像你收拾房间,有些东西要拿出来单独放,有些东西要合并到一起。今天要讲的这四种手法,是重构里最基础、也最常用的操作。我做了这么多年项目,几乎每天都会用到它们。
提炼函数:把一段逻辑「拎」出来
提炼函数,就是把一段有明确目的的代码,单独抽成一个函数。为什么要这么做?因为代码的可读性会大幅提升。
举个例子,你可能会写出这样的代码:
function printOwing(invoice) {
let outstanding = 0;
// 计算未付金额
for (const o of invoice.orders) {
outstanding += o.amount;
}
// 打印账单
console.log(`客户:${invoice.customer}`);
console.log(`金额:${outstanding}`);
}
这段代码里,计算和打印混在一起。我建议把计算部分提炼出来:
function printOwing(invoice) {
const outstanding = calculateOutstanding(invoice);
printBill(invoice, outstanding);
}
function calculateOutstanding(invoice) {
let result = 0;
for (const o of invoice.orders) {
result += o.amount;
}
return result;
}
function printBill(invoice, outstanding) {
console.log(`客户:${invoice.customer}`);
console.log(`金额:${outstanding}`);
}
你看,主函数变得非常清晰。它只负责「做什么」,不关心「怎么做」。
calculateOutstanding、printBill。这样读代码的人一眼就知道这个函数在干什么。
内联函数:把多余的「壳」去掉
内联函数和提炼函数正好相反。当一个函数太简单,或者它的逻辑已经不言自明时,我会把它内联回去。
我在项目中遇到过这样的情况:有人写了一个函数,里面只有一行代码,而且函数名和实现几乎一样。比如:
function getRating(driver) {
return moreThanFiveLateDeliveries(driver) ? 2 : 1;
}
function moreThanFiveLateDeliveries(driver) {
return driver.numberOfLateDeliveries > 5;
}
这个 moreThanFiveLateDeliveries 函数,其实没有提供额外的信息。直接内联掉:
function getRating(driver) {
return driver.numberOfLateDeliveries > 5 ? 2 : 1;
}
代码反而更短、更直接。你想想看,如果每个函数都只有一行,那还不如直接写出来。
提炼变量:给表达式一个名字
复杂的表达式很难读懂。提炼变量就是给这些表达式起个名字,让代码自解释。
比如这段代码:
if (order.price * order.quantity > 1000) {
// 处理大额订单
}
这个条件判断是什么意思?1000 是什么?提炼变量后:
const isLargeOrder = order.price * order.quantity > 1000;
if (isLargeOrder) {
// 处理大额订单
}
现在代码自己会说话。你不需要注释,就能明白这段逻辑。
我曾经在一个遗留系统里看到过这样的代码:
if (user.role === 'admin' && user.status === 'active' && user.lastLogin > Date.now() - 86400000) {
// 允许访问
}
86400000 是什么?是 24 小时的毫秒数。我提炼了三个变量:
const isAdmin = user.role === 'admin';
const isActive = user.status === 'active';
const isRecentLogin = user.lastLogin > Date.now() - ONE_DAY_MS;
if (isAdmin && isActive && isRecentLogin) {
// 允许访问
}
现在这段代码,任何人都能一眼看懂。
内联变量:去掉不必要的中间人
内联变量是提炼变量的逆操作。当一个变量名没有提供额外信息,或者只被使用一次时,我会把它内联掉。
举个例子:
const basePrice = order.basePrice;
const finalPrice = basePrice * (1 - order.discount);
return finalPrice;
这里的 basePrice 和 finalPrice 都只使用了一次。直接内联:
return order.basePrice * (1 - order.discount);
代码更紧凑,而且没有丢失任何信息。你想想看,如果每个中间结果都用一个变量保存,代码会变得又长又啰嗦。
四种手法的关系
这四种手法其实是两对「反向操作」:
- 提炼函数 ↔ 内联函数:一个把代码抽出来,一个把代码放回去
- 提炼变量 ↔ 内联变量:一个给表达式命名,一个去掉不必要的名字
它们不是非此即彼的选择。在实际项目中,我会根据代码的上下文来决定用哪一种。比如,一个函数如果越来越复杂,我就提炼;如果越来越简单,我就内联。
什么时候用哪种?
我总结了一个简单的判断逻辑:
| 场景 | 推荐手法 | 原因 |
|---|---|---|
| 一段代码有明确目的,且被注释标记 | 提炼函数 | 用函数名代替注释 |
| 一个函数只有一行,且调用处很少 | 内联函数 | 消除不必要的间接层 |
| 复杂表达式,难以一眼看懂 | 提炼变量 | 给表达式起个有意义的名字 |
| 变量名和表达式含义重复 | 内联变量 | 去掉多余的中间人 |
嗯,这四种手法其实很简单,但用好了能大幅提升代码质量。我建议你在日常开发中多练习,慢慢就会形成直觉——看到一段代码,就知道该提炼还是该内联。
记住一句话:好的代码,读起来像是一篇好文章。每个函数、每个变量,都应该有它存在的理由。