12、组织数据(上):拆分变量、字段改名、以查询取代派生变量

数据是代码的血液。我见过太多系统,逻辑写得还行,但数据模型一团糟,最后改一个需求要动十几个地方。说白了,数据组织不好,重构就是空中楼阁。

这一章我们聊三个最常用的数据重构手法。它们看起来简单,但用好了,能让你的代码结构清晰一大截。

12.1 拆分变量:别让一个变量干两件事

你想想看,一个变量在函数里被赋值好几次,每次含义还不一样。这种代码我见过太多次了。比如下面这个例子:

// 重构前:temp 变量身兼数职
function calculate() {
  let temp = 2 * (height + width);
  console.log(temp);
  temp = height * width;
  console.log(temp);
}

这里的 temp 先是周长,后是面积。读代码的人得记住上下文才能理解。我个人习惯是:一个变量只承担一个职责。

// 重构后:每个变量只表达一个含义
function calculate() {
  const perimeter = 2 * (height + width);
  console.log(perimeter);
  const area = height * width;
  console.log(area);
}
我的经验:拆分变量时,记得用 const 声明新变量。这样既能防止意外修改,也明确告诉读者——这个值不会变。

什么时候该拆?我有个简单判断标准:如果你看到一个变量在代码中被赋值超过一次,而且每次的含义不同,那就拆。别犹豫。

12.2 字段改名:名字就是最好的文档

字段命名这件事,我踩过不少坑。曾经有个项目,一个字段叫 n,我以为是数量,结果发现是名称缩写。这种命名,三个月后连写代码的人自己都看不懂。

字段改名的核心原则:名字要准确反映字段的用途和含义

// 重构前:模糊的命名
class Organization {
  constructor(data) {
    this._name = data.name;
    this._country = data.country;
  }
}

这个 _name 是什么名字?公司名?部门名?还是联系人名?不清楚。

// 重构后:明确的命名
class Organization {
  constructor(data) {
    this._title = data.title;  // 公司名称
    this._country = data.country;
  }
}
改名技巧:先在类内部添加新字段,让旧字段委托给新字段。等所有调用方都迁移完成后,再删除旧字段。这叫「渐进式改名」,风险最小。

我曾经在一个大型系统中改字段名,直接全局替换,结果漏了一个配置文件,线上出了 bug。从那以后,我改字段名一定分三步走:

  1. 新增字段,旧字段保留
  2. 逐步迁移所有引用点
  3. 确认无误后删除旧字段

12.3 以查询取代派生变量:别让数据不一致

这是我最喜欢的手法之一。很多程序员喜欢把派生数据存成变量,觉得这样「省事」。但你想过没有,一旦原始数据变了,派生变量就过时了。

// 重构前:手动维护派生变量
class Discount {
  constructor(basePrice) {
    this._basePrice = basePrice;
    this._discountedPrice = basePrice * 0.9;  // 派生变量
  }
  
  applyExtraDiscount() {
    this._discountedPrice = this._basePrice * 0.8;  // 容易忘记同步
  }
}

这里的问题很明显:如果 _basePrice 变了,_discountedPrice 不会自动更新。你想想看,这种 bug 多难排查。

// 重构后:用查询函数取代派生变量
class Discount {
  constructor(basePrice) {
    this._basePrice = basePrice;
  }
  
  get discountedPrice() {
    return this._basePrice * 0.9;
  }
  
  applyExtraDiscount() {
    // 不再需要维护派生变量
  }
}
注意:如果计算派生变量的开销很大,或者调用频率极高,那保留派生变量也有道理。但这种情况很少见。大多数时候,查询函数更安全。

我遇到过最离谱的情况:一个订单系统里,totalAmountitems 数组里的商品价格不一致。查了两天才发现,是有人直接修改了 totalAmount 字段,没同步商品列表。用查询取代派生变量后,这种问题再也没出现过。

知识体系总览

下面这张图总结了本章三个手法的核心逻辑和适用场景:

组织数据(上):三大手法 拆分变量 问题:一个变量多次赋值 含义不同,难以理解 做法: 1. 识别不同含义 2. 分别声明新变量 3. 使用 const 声明 适用:函数内临时变量 字段改名 问题:字段名含义模糊 难以理解用途 做法: 1. 新增明确字段 2. 逐步迁移调用方 3. 删除旧字段 适用:类/对象的属性 查询取代派生变量 问题:派生变量不同步 数据不一致 做法: 1. 删除派生变量 2. 编写查询函数 3. 每次实时计算 适用:可由其他字段 计算得出的值 核心原则:每个数据只表达一个含义,且只在一个地方定义

避坑指南

我曾经在一个项目中,把派生变量改成查询函数后,性能下降了 30%。原因是那个查询函数里有个循环,每次调用都遍历整个列表。后来加了缓存才解决。

所以我的建议是:

  • 先改成查询函数,保证正确性
  • 如果性能有问题,再考虑加缓存或优化算法
  • 不要为了「可能的性能问题」而保留不安全的派生变量
总结一下:拆分变量让代码更易读,字段改名让意图更清晰,查询取代派生变量让数据更一致。这三个手法虽然基础,但用好了,代码质量能上一个台阶。

公众号:蓝海资料掘金营,微信deep3321