重新组织数据
拆分变量
反向重构
动机
[!info] 变量有各种不同的用途,其中某些用途会很自然地导致临时变量被多次赋值。“循环变量”和“结果收集变量”就是两个典型例子:循环变量(loop variable)会随循环的每次运行而改变(例如for(let i=0; i<10; i++)语句中的i);结果收集变量(collecting variable)负责将“通过整个函数的运算”而构成的某个值收集起来。
除了这两种情况,还有很多变量用于保存一段冗长代码的运算结果,以便稍后使用。这种变量应该只被赋值一次。如果它们被赋值超过一次,就意味它们在函数中承担了一个以上的责任。
如果变量承担多个责任,它就应该被替换(分解)为多个变量,每个变量只承担一个责任。同一个变量承担两件不同的事情,会令代码阅读者糊涂。
范例: 对输入参数赋值
重构前:
function discount (inputValue, quantity) {
if (inputValue > 50) inputValue = inputValue - 2;
if (quantity > 100) inputValue = inputValue - 1;
return inputValue;
}
重构后:
function discount (inputValue, quantity) {
let result = inputValue;
if (inputValue > 50) result = result - 2;
if (quantity > 100) result = result - 1;
return result;
}
字段改名
反向重构
动机
[!info] 命名很重要,对于程序中广泛使用的记录结构,其中字段的命名格外重要。数据结构对于帮助阅读者理解特别重要。
范例
重构前:
重构后:
以查询取代派生变量
反向重构
动机
[!info] 有些变量其实可以很容易地随时计算出来。如果能去掉这些变量,也算朝着消除可变性的方向迈出了一大步。计算常能更清晰地表达数据的含义,而且也避免了“源数据修改时忘了更新派生变量”的错误。
有一种合理的例外情况:如果计算的源数据是不可变的,并且我们可以强制要求计算的结果也是不可变的,那么就不必重构消除计算得到的派生变量
范例
重构前:
class ProductionPlan{
get production() {return this._production;}
applyAdjustment(anAdjustment) {
this._adjustments.push(anAdjustment);
this._production += anAdjustment.amount;
}
}
重构后:
class ProductionPlan{
get production(){
return this._adjustments.reduce((sum, a) => sum + a.amount, 0);
}
applyAdjustment(anAdjustment) {
this._adjustments.push(anAdjustment);
}
}
将引用对象改为值对象
反向重构
[[重构:改善既有的代码设计#将值对象改为引用对象|将值对象改为引用对象]]
动机
[!info] 如果把一个字段视为值对象,我可以把内部对象的类也变成值对象[mf-vo]。值对象通常更容易理解,主要因为它们是不可变的。
一般说来,不可变的数据结构处理起来更容易。我可以放心地把不可变的数据值传给程序的其他部分,而不必担心对象中包装的数据被偷偷修改。我可以在程序各处复制值对象,而不必操心维护内存链接。值对象在分布式系统和并发系统中尤为有用。
范例
重构前:
class Person{
constructor() {
constructor() {
this._telephoneNumber = new TelephoneNumber();
}
get officeAreaCode() {return this._telephoneNumber.areaCode;}
set officeAreaCode(arg) {this._telephoneNumber.areaCode = arg;}
get officeNumber() {return this._telephoneNumber.number;}
set officeNumber(arg) {this._telephoneNumber.number = arg;}
}
class TelephoneNumber{
get areaCode() {return this._areaCode;}
set areaCode(arg) {this._areaCode = arg;}
get number() {return this._number;}
set number(arg) {this._number = arg;}
}
重构后:
class Person{
constructor() {
constructor() {
this._telephoneNumber = new TelephoneNumber();
}
get officeAreaCode() {return this._telephoneNumber.areaCode;}
set officeAreaCode(arg) {
this._telephoneNumber = new TelephoneNumber(arg, this.officeNumber);
}
get officeNumber() {return this._telephoneNumber.number;}
set officeNumber(arg) {
this._telephoneNumber = new TelephoneNumber(this.officeAreaCode, arg);
}
}
class TelephoneNumber{
constructor(areaCode, number) {
this._areaCode = areaCode;
this._number = number;
}
}
将值对象改为引用对象
反向重构
[[重构:改善既有的代码设计#将引用对象改为值对象|将引用对象改为值对象]]
动机
[!info] 把值对象改为引用对象会带来一个结果:对于一个客观实体,只有一个代表它的对象。这通常意味着我会需要某种形式的仓库,在仓库中可以找到所有这些实体对象。只为每个实体创建一次对象,以后始终从仓库中获取该对象。 可以理解为这是一种单例模式。
范例
重构前:
class Order{
constructor(data) {
this._number = data.number;
this._customer = new Customer(data.customer);
// load other data
}
get customer() {return this._customer;}
}
class Customer{
constructor(id) {
this._id = id;
}
get id() {return this._id;}
}
重构后:
// file repos
let _repositoryData;
export function initialize() {
_repositoryData = {};
_repositoryData.customers = new Map();
}
export function registerCustomer(id) {
if (!_repositoryData.customers.has(id))
_repositoryData.customers.set(id, new Customer(id));
return findCustomer(id);
}
export function findCustomer(id) {
return _repositoryData.customers.get(id);
}
class Order{
constructor(data) {
this._number = data.number;
this._customer = registerCustomer(data.customer);
// load other data
}
get customer() {return this._customer;}
}