《重构》笔记摘录 - 6.重新组织数据

1 拆分变量(Split Variable)

变量有各种不同的用途,其中某些用途会很自然地导致临时变量被多次赋值。

"循环变量"和"结果收集变量"就是两个典型例子:循环变量会随循环的每次运行而改变;结果收集变量负责将通过整个函数的运算而构成的某个值收集起来。

除了这两种情况,还有很多变量用于保存一段冗长代码的运算结果,以便稍后使用。这种变量应该只被赋值一次。如果它们被赋值超过一次,就意味着它们在函数中承担了一个以上的责任。

如果变量承担多个责任,它就应该被替换(分解)为多个变量,每个变量只承担一个责任。同一个变量承担两件不同的事情,会让代码阅读者糊涂。

2字段改名(Rename Field)

命名很重要,对于程序中广泛使用的记录结构,其中字段的命名格外重要。

数据结构对于帮助阅读者理解特别重要。多年以前,Fred Brooks 就说过:"只给我看你的工作流程却隐藏表单,我将仍然一头雾水。但是如果你给我展示表单,或许不需要流程图,就能柳暗花明。"现在已经不太有人画流程图了,不过道理还是一样的。

数据结构是理解程序行为的关键。既然数据结构如此重要,就很有必要保持它们的整洁。一如既往地,我在一个软件上做的工作越多,对数据的理解就越深,所以很有必要把我加深的理解融入程序中。

记录结构中的字段可能需要改名,类的字段也一样。在类的使用者看来,取值和设值函数就等于是字段。对这些函数的改名,跟裸记录结构的字段改名一样重要。

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

可变数据是软件中最大的错误源头之一。对数据的修改常常导致代码的各个部分以丑陋的形式互相耦合:在一处修改数据,却在另一处造成难以发现的破坏。

很多时候,完全去掉可变数据并不现实,但我还是强烈建议:尽量把可变数据的作用域限制在最小范围。有些变量其实可以很突易地随时计算出来。如果能去掉这些变量,也算朝着消除可变性的方向迈出了一大步。计算常能更清晰地表达数据的含义,而且也避免了"源数据修改时忘了更新派生变量"的错误。

有一种合理的例外情况:如果计算的源数据是不可变的,并且我们可以强制要求计算的结果也是不可变的,那么就不必重构消除计算得到的派生变量。因此,"根据源数据生成新数据结构"的变换操作可以保持不变,即便我们可以将其替换为计算操作。

实际上,这是两种不同的编程风格:一种是对象风格,把一系列计算得出的属性包装在数据结构中;另一种是函数风格,将一个数据结构变换为另一个数据结构。如果源数据会被修改,而你必须负责管理派生数据结构的整个生命周期,那么对象风格显然更好。但如果源数据不可变,或者派生数据用过即弃,那么两种风格都可行。

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

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

如果把一个字段视为值对象,我可以把内部对象的类也变成值对象。值对象通常更容易理解,主要因为它们是不可变的。

一般说来,不可变的数据结构处理起来更容易。我可以放心地把不可变的数据值传给程序的其他部分,而不必担心对象中包装的数据被偷偷修改。我可以在程序各处复制值对象,而不必操心维护内存链接。值对象在分布式系统和并发系统中尤为有用。值对象和引用对象的区别也告诉我,何时不应该使用本重构手法。如果我想在几个对象之间共享一个对象,以便几个对象都能看见对共享对象的修改,那么这个共享的对象就应该是引用。

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

一个数据结构中可能包含多个记录,而这些记录都关联到同一个逻辑数据结构。

例如,我可能会读取一系列订单数据,其中有多条订单属于同一个顾客。遇到这样的共享关系时,既可以把顾客信息作为值对象来看待,也可以将其视为引用对象。如果将其视为值对象,那么每份订单数据中都会复制顾客的数据;而如果将其视为引用对象,对于一个顾客,就只有一份数据结构,会有多个订单与之关联。如果顾客数据永远不修改,那么两种处理方式都合理。

把同一份数据复制多次可能会造成一点困扰,但这种情况也很常见,不会造成太大问题。过多的数据复制有可能会造成内存占用的问题,但就跟所有性能问题一样,这种情况并不常见。如果共享的数据需要更新,将其复制多份的做法就会遇到巨大的困难。此时我必须找到所有的副本,更新所有对象。只要漏掉一个副本没有更新,就会遭遇麻烦的数据不一致。这种情况下,可以考虑将多份数据副本变成单一的引用,这样对顾客数据的修改就会立即反映在该顾客的所有订单中。

把值对象改为引用对象会带来一个结果:对于一个客观实体,只有一个代表它的对象。这通常意味着我会需要某种形式的仓库,在仓库中可以找到所有这些实体对象。只为每个实体创建一次对象,以后始终从仓库中获取该对象。

相关推荐
cwtlw1 小时前
PhotoShop学习09
笔记·学习·其他·photoshop
一棵开花的树,枝芽无限靠近你2 小时前
【CodeMirror】系列(二)官网示例(六)自动补全、边栏
前端·笔记·学习·编辑器·codemirror
pumpkin845142 小时前
学习笔记四——Rust 函数通俗入门
笔记·学习·rust
pumpkin845143 小时前
学习笔记九——Rust所有权机制
笔记·学习·rust
热爱极限运动的码农3 小时前
25某团校招后端开发一面
java·笔记·网络协议·学习·进程线程
牛十二3 小时前
java零基础教学笔记
笔记
见青..4 小时前
【学习笔记】文件上传漏洞--中间件解析漏洞、编辑器安全
笔记·学习·安全
IT信息技术学习圈4 小时前
网络安全课程笔记整理
网络·笔记·web安全
珊瑚里的鱼4 小时前
【双指针】专题:LeetCode 283题解——移动零
开发语言·c++·笔记·算法·leetcode·职场和发展
freexyn5 小时前
Matlab学习笔记五十:循环语句和条件语句的用法
笔记·学习·matlab