1、将查询函数和修改函数分离(Separate Query from Modifier)
例如:
function getTotalOutstandingAndSendBill() {
// 查询
const result = customer.invoices.reduce((total, each) => each.amount + total, 0);
// 修改
sendBill();
return result;
}
改为:
// 查询
function totalOutstanding() {
return customer.invoices.reduce((total, each) => each.amount + total, 0);
}
// 修改
function sendBill() {
emailGateway.send(formatBill(customer));
}
2、函数参数化(Parameterize Function)
如果两个函数逻辑非常相似,只有一些字面量值不同,可以将其合并成一个函数,以参数的形式传入不同的值,从而消除重复。
3、移除标记参数(Remove Flag Argument)
“标记参数”是这样的一种参数:调用者用它来指示被调函数应该执行哪一部分逻辑。
标记参数让人难以理解到底有哪些函数可以调用、应该怎么调用。拿到一份API以后,我首先看到的是一系列可供调用的函数,但标记参数却隐藏了函数调用中存在的差异性。使用这样的函数,我还得弄清标记参数有哪些可用的值。
如果一个函数有多个标记参数,可能就不得不将其保留,否则我就得针对各个参数的各种取值的所有组合情况提供明确函数。不过这也是一个信号,说明这个函数可能做得太多,应该考虑是否能用更简单的函数来组合出完整的逻辑。
重构例子:
function setDimension(name, value) {
if (name === "height") {
this._height = value;
return;
}
if (name === "width") {
this._width = value;
return;
}
}
改为:
function setHeight(value) {this._height = value;}
function setWidth (value) {this._width = value;}
4、 保持对象完整(Preserve Whole Object)
如果我看见代码从一个记录结构中导出几个值,然后又把这几个值一起传递给一个函数,我会更愿意把整个记录传给这个函数,在函数体内部导出所需的值。
例如:
const low = aRoom.daysTempRange.low;
const high = aRoom.daysTempRange.high;
if (aPlan.withinRange(low, high))
改为:
if (aPlan.withinRange(aRoom.daysTempRange))
5、以查询取代参数(Replace Parameter with Query)
函数的参数列表应该总结该函数的可变性,标示出函数可能体现出行为差异的主要方式。和任何代码中的语句一样,参数列表应该尽量避免重复,并且参数列表越短就越容易理解。
例如:
availableVacation(anEmployee, anEmployee.grade);
function availableVacation(anEmployee, grade) {
// calculate vacation...
改为:
availableVacation(anEmployee)
function availableVacation(anEmployee) {
const grade = anEmployee.grade;
// calculate vacation...
6、以参数取代查询(Replace Query with Parameter)
在浏览函数实现时,我有时会发现一些令人不快的引用关系,例如,引用一个全局变量,或者引用另一个我想要移除的元素。为了解决这些令人不快的引用,我需要将其替换为函数参数,从而将处理引用关系的责任转交给函数的调用者。
需要使用本重构的情况大多源于我想要改变代码的依赖关系——为了让目标函数不再依赖于某个元素,我把这个元素的值以参数形式传递给该函数。
7、移除设值函数(Remove Setting Method)
如果不希望在对象创建之后此字段还有机会被改变,那就不要为它提供设值函数(同时将该字段声明为不可变)。
这里个人认为有些偏激,不过作者的目的是要强调不可变性。
8、以工厂函数取代构造函数(Replace Constructor with Factory Function)
构造函数常有一些丑陋的局限性。以Java的构造函数为例:
- 只能返回当前所调用类的实例。也就是说,我无法根据环境或参数信息返回子类实例或代理对象;
- 构造函数的名字是固定的,因此无法使用比默认名字更清晰的函数名;
- 构造函数需要通过特殊的操作符来调用(在很多语言中是new关键字),所以在要求普通函数的场合就难以使用。
工厂函数就不受这些限制。工厂函数的实现内部可以调用构造函数,但也可以换成别的方式实现。