11、搬移特性(下):以函数调用取代内联代码、移动语句、拆分循环、以管道取代循环

好,咱们接着聊搬移特性。上一章讲了搬移函数和搬移字段,这次我把剩下的几个手法一并说完。这几个手法看起来小,但实际用起来特别顺手。我自己的经验是,它们就像代码里的“微整形”,不动大手术,但能让代码顺眼很多。

以函数调用取代内联代码

什么叫内联代码?说白了,就是一段逻辑直接写在调用处,没有封装成函数。比如你看到一段代码,明明在好几个地方都做了类似的事情,但每次都是手写一遍。嗯,这时候就该把它抽出来。

我举个例子。假设你有一段代码,用来计算订单的折扣:

let basePrice = quantity * itemPrice;
let discount = Math.max(0, quantity - 100) * 0.05;
let finalPrice = basePrice - discount;

这段逻辑如果出现在多个地方,每次都要重复写一遍。你想想看,万一哪天折扣规则变了,你得改多少个地方?

正确的做法是把它封装成一个函数:

function calculateDiscount(quantity, itemPrice) {
    let basePrice = quantity * itemPrice;
    let discount = Math.max(0, quantity - 100) * 0.05;
    return basePrice - discount;
}

然后调用处就变成:

let finalPrice = calculateDiscount(quantity, itemPrice);
我的习惯:只要一段代码出现两次以上,我就会考虑把它抽成函数。别等到第三次,那时候你已经忘了之前写过什么了。

我在项目中遇到过一种情况:有人把一段 50 行的内联代码复制粘贴了 5 次,每次只改了一个变量名。后来需求变了,改代码的人差点崩溃。所以,别偷懒,该抽就抽。

移动语句

移动语句,听起来很简单,就是把代码行换个位置。但你别小看它,很多时候代码的可读性差,就是因为语句放错了地方。

举个例子:

let result = [];
let items = getItems();
let discount = 0.1;

for (let item of items) {
    // 处理每个 item
}

let taxRate = 0.05;

你看,discounttaxRate 这两个变量,一个放在循环前面,一个放在循环后面。读代码的人得来回跳着看。更好的做法是把相关的变量放在一起:

let items = getItems();
let discount = 0.1;
let taxRate = 0.05;
let result = [];

for (let item of items) {
    // 处理每个 item
}
注意:移动语句时一定要小心变量依赖。如果后面的语句依赖前面的计算结果,那就不能随便移动。我曾经因为移动语句导致了一个隐蔽的 bug,查了整整一个下午。

移动语句的核心原则是:让代码的阅读顺序和逻辑顺序一致。你想想看,读代码就像读故事,谁愿意看一段跳来跳去的情节?

拆分循环

这个手法我特别喜欢。很多程序员喜欢在一个循环里做所有事情,觉得这样性能好。但问题是,代码的可读性会变得很差。

比如这个循环:

let totalSalary = 0;
let youngestAge = Infinity;

for (let person of people) {
    totalSalary += person.salary;
    if (person.age < youngestAge) {
        youngestAge = person.age;
    }
}

一个循环做了两件事:计算总薪资和找最年轻的人。读代码的人得同时理解两个逻辑。拆开之后:

let totalSalary = 0;
for (let person of people) {
    totalSalary += person.salary;
}

let youngestAge = Infinity;
for (let person of people) {
    if (person.age < youngestAge) {
        youngestAge = person.age;
    }
}

有人会说:“这样性能不就差了吗?要遍历两次数组。” 嗯,这个问题我遇到过很多次。我的回答是:先让代码清晰,再考虑性能。大多数情况下,两次遍历的性能差异微乎其微。如果真的成了瓶颈,再优化也不迟。

核心观点:拆分循环不是为了性能,而是为了可读性。一个循环只做一件事,这是代码整洁之道的基本原则。

以管道取代循环

这个手法是函数式编程的体现。说白了,就是用 mapfilterreduce 这些管道操作来替代传统的 for 循环。

举个例子,假设你有一个员工列表,要找出薪资超过 5000 的员工姓名:

let result = [];
for (let employee of employees) {
    if (employee.salary > 5000) {
        result.push(employee.name);
    }
}

用管道改写:

let result = employees
    .filter(e => e.salary > 5000)
    .map(e => e.name);

你看,代码变得简洁多了。而且管道的每一步都清晰表达了意图:先过滤,再映射。

我个人习惯是,只要循环里只有简单的过滤和转换,就优先用管道。但要注意,如果循环里有复杂的副作用(比如修改外部变量),那还是用 for 循环更合适。

避坑指南:我曾经在一个项目里过度使用管道,把 5 个管道链在一起,结果调试的时候根本不知道哪一步出了问题。所以,管道虽好,但别贪多。一般 2-3 个管道链就够了,再多就该考虑拆分了。

知识体系总览

下面这张图总结了本章的四个手法,以及它们之间的关系:

搬移特性(下)知识体系 以函数调用取代内联代码 移动语句 拆分循环 以管道取代循环 核心目标:提升代码可读性与可维护性 每个手法都服务于同一个目标:让代码更容易理解和修改 先让代码清晰,再考虑性能优化

总结

这四个手法,说白了就是让你在写代码的时候多问自己几个问题:

  • 这段逻辑是不是应该封装成一个函数?
  • 这些语句放在这里是不是最合适的位置?
  • 这个循环是不是做了太多事情?
  • 能不能用更简洁的管道来表达?

我自己的经验是,养成这些习惯之后,代码质量会有明显的提升。而且你会发现,改代码的时候心情也会好很多——因为代码本身就在告诉你它想变成什么样子。

嗯,这一章就到这里。记住,代码整洁不是一蹴而就的,而是靠这些小手法一点一点打磨出来的。