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

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)

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

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

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

相关推荐
sealaugh3212 小时前
AI(学习笔记第十七课)langchain v1.0(SQL Agent)
人工智能·笔记·学习
山土成旧客12 小时前
【Python学习打卡-Day30】模块化编程:从“单兵作战”到“军团指挥”
开发语言·python·学习
wdfk_prog12 小时前
[Linux]学习笔记系列 -- [fs]iomap
linux·笔记·学习
糕......12 小时前
JDK安装与Java开发环境配置全攻略
java·开发语言·网络·学习
小宋加油啊12 小时前
强化学习 学习记录
学习
知识分享小能手12 小时前
Ubuntu入门学习教程,从入门到精通, Ubuntu 22.04 的磁盘存储管理(10)
linux·学习·ubuntu
2501_9098008112 小时前
Java IO框架
java·学习·io框架
●VON12 小时前
双重修炼:如何兼顾专业学习与博客创作的 2025 复盘
学习·von·25年度总结
做cv的小昊21 小时前
计算机图形学:【Games101】学习笔记05——着色(插值、高级纹理映射)与几何(基本表示方法)
笔记·opencv·学习·计算机视觉·图形渲染·几何学
车载测试工程师21 小时前
CAPL学习-CAN相关函数-统计API函数
网络·网络协议·学习·capl·canoe