重构改善既有代码的设计-学习(三):重新组织数据

1、拆分变量(Split Variable)

有些变量用于保存一段冗长代码的运算结果,以便稍后使用。这种变量应该只被赋值一次。

如果它们被赋值超过一次,就意味它们在函数中承担了一个以上的责任。如果变量承担多个责任,它就应该被替换(分解)为多个变量,每个变量只承担一个责任。同一个变量承担两件不同的事情,会令代码阅读者糊涂。

有两种情况除外:

  • 循环变量(loop variable)会随循环的每次运行而改变(例如for(let i=0; i<10; i++)语句中的
    i)
  • 结果收集变量(collecting variable)负责将"通过整个函数的运算"而构成的某个值收集起来。

2、字段改名(Rename Field)

将字段改为易于理解的名字,不要出现类似 int a等。

3、以查询取代派生变量(Replace Derived Variable with Query)

可变数据是软件中最大的错误源头之一。对数据的修改常常导致代码的各个部分以丑陋的形式互相耦合:在一处修改数据,却在另一处造成难以发现的破坏。很多时候,完全去掉可变数据并不现实,但我还是强烈建议:尽量把可变数据的作用域限制在最小范围。

例如:

javascript 复制代码
get discountedTotal() {return this._discountedTotal;}
set discount(aNumber) {
    const old = this._discount;
    this._discount = aNumber;
    this._discountedTotal += old - aNumber;
}

改为:

javascript 复制代码
get discountedTotal() {return this._baseTotal - this._discount;}
set discount(aNumber) {this._discount = aNumber;}

4、将引用对象改为值对象(Change Reference to Value)

在把一个对象(或数据结构)嵌入另一个对象时,位于内部的这个对象可以被视为引用对象,也可以被视为值对象。两者最明显的差异在于如何更新内部对象的属性:如果将内部对象视为引用对象,在更新其属性时,我会保留原对象不动,更新内部对象的属性;如果将其视为值对象,我就会替换整个内部对象,新换上的对象会有我想要的属性值。

如果把一个字段视为值对象,我可以把内部对象的类也变成值对象[mf-vo]。值对象通常更容易理解,主要因为它们是不可变的。一般说来,不可变的数据结构处理起来更容易。我可以放心地把不可变的数据值传给程序的其他部分,而不必担心对象中包装的数据被偷偷修改。我可以在程序各处复制值对象,而不必操心维护内存链接。值对象在分布式系统和并发系统中尤为有用。

何时不应该使用本重构手法?如果我想在几个对象之间共享一个对象,以便几个对象都能看见对共享对象的修改,那么这个共享的对象就应该是引用。这便是5中的情况了。

4的例子:

javascript 复制代码
class Product {
    applyDiscount(arg) {this._price.amount -= arg;}
}

改为:

javascript 复制代码
class Product {
    applyDiscount(arg) {
        this._price = new Money(this._price.amount - arg, this._price.currency);
    }
}

5、将值对象改为引用对象(Change Value to Reference)

一个数据结构中可能包含多个记录,而这些记录都关联到同一个逻辑数据结构。例如,我可能会读取一系列订单数据,其中有多条订单属于同一个顾客。遇到这样的共享关系时,既可以把顾客信息作为值对象来看待,也可以将其视为引用对象。如果将其视为值对象,那么每份订单数据中都会复制顾客的数据;而如果将其视为引用对象,对于一个顾客,就只有一份数据结构,会有多个订单与之关联。

如果顾客数据永远不修改,那么两种处理方式都合理。把同一份数据复制多次可能会造成一点困扰,但这种情况也很常见,不会造成太大问题。过多的数据复制有可能会造成内存占用的问题,但就跟所有性能问题一样,这种情况并不常见。

如果共享的数据需要更新,将其复制多份的做法就会遇到巨大的困难。此时我必须找到所有的副本,更新所有对象。只要漏掉一个副本没有更新,就会遭遇麻烦的数据不一致。这种情况下,可以考虑将多份数据副本变成单一的引用,这样对顾客数据的修改就会立即反映在该顾客的所有订单中。

相关推荐
佳哥的技术分享11 小时前
Function<T, R> 中 apply,compose, andThen 方法总结
java·学习·r语言
2501_9402778012 小时前
告别碎片化集成:使用 MCP 标准化重构企业内部遗留 API,构建统一的 AI 原生接口中心
人工智能·重构
2501_9421584312 小时前
服务设计从成本到利润引擎的重构
大数据·python·重构
阳明Coding12 小时前
golang从入门到通天—数据库操作(gorm框架使用)(最简单最详细的golang学习笔记)
笔记·学习·golang
AI视觉网奇12 小时前
ue5 开发 web socket server 实战2026
c++·学习·ue5
嗯嗯=12 小时前
STM32单片机学习篇6
stm32·单片机·学习
2301_7973122612 小时前
学习Java42天
java·开发语言·学习
找了一圈尾巴12 小时前
智能体自演进框架-ACE(论文学习)
学习·提示词
ValidationExpression12 小时前
学习:企业标准的容器化 CI,CD 发布流程
学习·ci/cd
式51612 小时前
大模型学习基础(九)LoRA微调原理
人工智能·深度学习·学习