曾用名:以函数取代参数(Replace Parameter with Method)
反向重构:以参数取代查询(327)
availableVacation(anEmployee, anEmployee.grade);
function availableVacation(anEmployee, grade) {
// calculate vacation...
availableVacation(anEmployee)
function availableVacation(anEmployee) {
const grade = anEmployee.grade;
// calculate vacation...动机
函数的参数列表应该总结该函数的可变性,标示出函数可能体现出行为差异的主要方式。和任何代码中的语句一样,参数列表应该尽量避免重复,并且参数列表越短就越容易理解。
如果调用函数时传入了一个值,而这个值由函数自己来获得也是同样容易,这就是重复。这个本不必要的参数会增加调用者的难度,因为它不得不找出正确的参数值,其实原本调用者是不需要费这个力气的。
“同样容易”四个字,划出了一条判断的界限。去除参数也就意味着“获得正确的参数值”的责任被转移:有参数传入时,调用者需要负责获得正确的参数值;参数去除后,责任就被转移给了函数本身。一般而言,我习惯于简化调用方,因此我愿意把责任移交给函数本身,但如果函数难以承担这份责任,就另当别论了。
不使用以查询取代参数最常见的原因是,移除参数可能会给函数体增加不必要的依赖关系——迫使函数访问某个程序元素,而我原本不想让函数了解这个元素的存在。这种“不必要的依赖关系”除了新增的以外,也可能是我想要稍后去除的,例如为了去除一个参数,我可能会在函数体内调用一个有问题的函数,或是从一个对象中获取某些原本想要剥离出去的数据。在这些情况下,都应该慎重考虑使用以查询取代参数。
如果想要去除的参数值只需要向另一个参数查询就能得到,这是使用以查询取代参数最安全的场景。如果可以从一个参数推导出另一个参数,那么几乎没有任何理由要同时传递这两个参数。
另外有一件事需要留意:如果在处理的函数具有引用透明性(referential transparency,即,不论任何时候,只要传入相同的参数值,该函数的行为永远一致),这样的函数既容易理解又容易测试,我不想使其失去这种优秀品质。我不会去掉它的参数,让它去访问一个可变的全局变量。
做法
如果有必要,使用提炼函数(106)将参数的计算过程提炼到一个独立的函数中。
将函数体内引用该参数的地方改为调用新建的函数。每次修改后执行测试。
全部替换完成后,使用改变函数声明(124)将该参数去掉。
范例
某些重构会使参数不再被需要,这是我最常用到以查询取代参数的场合。考虑下列代码。
class Order…
get finalPrice() {
const basePrice = this.quantity * this.itemPrice;
let discountLevel;
if (this.quantity > 100) discountLevel = 2;
else discountLevel = 1;
return this.discountedPrice(basePrice, discountLevel);
}
discountedPrice(basePrice, discountLevel) {
switch (discountLevel) {
case 1: return basePrice * 0.95;
case 2: return basePrice * 0.9;
}
}在简化函数逻辑时,我总是热衷于使用以查询取代临时变量(178),于是就得到了如下代码。
class Order…
get finalPrice() {
const basePrice = this.quantity * this.itemPrice;
return this.discountedPrice(basePrice, this.discountLevel);
}
get discountLevel() {
return (this.quantity > 100) ? 2 : 1;
}到这一步,已经不需要再把 discountLevel 的计算结果传给 discountedPrice 了,后者可以自己调用 discountLevel 函数,不会增加任何难度。
因此,我把 discountedPrice 函数中用到这个参数的地方全都改为直接调用 discountLevel 函数。
class Order…
discountedPrice(basePrice, discountLevel) {
switch (this.discountLevel) {
case 1: return basePrice * 0.95;
case 2: return basePrice * 0.9;
}
}然后用改变函数声明(124)手法移除该参数。
class Order…
get finalPrice() {
const basePrice = this.quantity * this.itemPrice;
return this.discountedPrice(basePrice, this.discountLevel);
}
discountedPrice(basePrice, discountLevel) {
switch (this.discountLevel) {
case 1: return basePrice * 0.95;
case 2: return basePrice * 0.9;
}
}