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。从那以后,我改字段名一定分三步走:
- 新增字段,旧字段保留
- 逐步迁移所有引用点
- 确认无误后删除旧字段
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() {
// 不再需要维护派生变量
}
}
我遇到过最离谱的情况:一个订单系统里,totalAmount 和 items 数组里的商品价格不一致。查了两天才发现,是有人直接修改了 totalAmount 字段,没同步商品列表。用查询取代派生变量后,这种问题再也没出现过。
知识体系总览
下面这张图总结了本章三个手法的核心逻辑和适用场景:
避坑指南
我曾经在一个项目中,把派生变量改成查询函数后,性能下降了 30%。原因是那个查询函数里有个循环,每次调用都遍历整个列表。后来加了缓存才解决。
所以我的建议是:
- 先改成查询函数,保证正确性
- 如果性能有问题,再考虑加缓存或优化算法
- 不要为了「可能的性能问题」而保留不安全的派生变量