《重构:改善既有的代码设计》读书笔记(五)

35 阅读3分钟

重新组织数据

拆分变量

反向重构

动机

[!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;}
  }