重构——搬移特性
- 搬移函数
- 搬移字段
- 搬移语句到函数,搬移语句到调用者
- 以函数调用取代内联代码
- 移动语句
- 拆分循环
- 移除无用代码
一、搬移函数
动机:函数频繁引用其他上下文中的元素,而对自身上下文中的元素却关心甚少
1、搬移函数至顶层
重构前
function trackSummary(points) {
const totalTime = calculateTime();
const totalDistance = calculateDistance();
const pace = totalTime / 60 / totalDistance ;
return {
time: totalTime,
distance: totalDistance,
pace: pace
};
function calculateDistance() {
let result = 0;
for (let i = 1; i < points.length; i++) {
result += distance(points[i-1], points[i]);
}
return result;
}
function distance(p1,p2) { ... }
function radians(degrees) { ... }
}
希望把calculateDistance函数搬移到顶层,单独计算轨迹的距离,不必算出汇总报告(summary)中的其他部分。
重构后
function trackSummary(points) {
const totalTime = calculateTime();
const pace = totalTime / 60 / totalDistance(points) ;
return {
time: totalTime,
distance: totalDistance(points),
pace: pace
};
}
function totalDistance(points) {
let result = 0;
for (let i = 1; i < points.length; i++) {
result += distance(points[i-1], points[i]);
}
return result;
}
2、类之间搬移函数
根据账户类型(account type)的不 同,决定不同的“透支金额计费”算法
重构前
class Account...
get bankCharge() {
let result = 4.5;
if (this._daysOverdrawn > 0) result += this.overdraftCharge;
return result;
}
get overdraftCharge() {
if (this.type.isPremium) {
const baseCharge = 10;
if (this.daysOverdrawn <= 7)
return baseCharge;
else
return baseCharge + (this.daysOverdrawn - 7) * 0.85;
}
else
return this.daysOverdrawn * 1.75;
}
重构后
class Account...
get bankCharge() {
let result = 4.5;
if (this._daysOverdrawn > 0) result += this.overdraftCharge;
return result;
}
get overdraftCharge() {
return this.type.overdraftCharge(this);
}
class AccountType…
overdraftCharge(account) {
if (this.isPremium) {
const baseCharge = 10;
if (account.daysOverdrawn <= 7)
return baseCharge;
else
return baseCharge + (account.daysOverdrawn - 7) * 0.85;
}
else
return account.daysOverdrawn * 1.75;
}
二、搬移字段
发现每当调用某个函数时,除了传入一个记录参数,还总是需要同时传入另一 条记录的某个字段一起作为参数
修改的难度也是引起我注意的一个原因, 如果修改一条记录时,总是需要同时改动另一条记录,那么 说明很可能有字段放错了位置
重构前
class Customer...
constructor(name, discountRate) {
this._name = name;
this._discountRate = discountRate;
this._contract = new CustomerContract(dateToday());
}
get discountRate() {return this._discountRate;}
becomePreferred() {
this._discountRate += 0.03;
// other nice things
}
applyDiscount(amount) {
return amount.subtract(amount.multiply(this._discountRate));
}
class CustomerContract...
constructor(startDate) {
this._startDate = startDate;
}
重构后
class Customer...
constructor(name, discountRate) {
this._name = name;
this._setDiscountRate(discountRate);
this._contract = new CustomerContract(dateToday());
}
get discountRate() {return this._contract.discountRate;}
_setDiscountRate(aNumber) {this._contract.discountRate = aNumber;}
becomePreferred() {
this._setDiscountRate(this.discountRate + 0.03);
// other nice things
}
applyDiscount(amount) {
return amount.subtract(amount.multiply(this.discountRate));
}
class CustomerContract...
constructor(startDate, discountRate) {
this._startDate = startDate;
this._discountRate = discountRate;
}
get discountRate() {return this._discountRate;}
set discountRate(arg) {this._discountRate = arg;}
搬移字段到共享对象
代表“账户”的Account类,类上有一个代表“利率”的字段 _interestRate,不希望让每个账户自己维护一个利率字段,利率应该取决于账户本身的类型,因此想将它搬移到AccountType中 去。
重构前
class Account...
constructor(number, type, interestRate) {
this._number = number;
this._type = type;
this._interestRate = interestRate;
}
get interestRate() {return this._interestRate;}
class AccountType...
constructor(nameString) {
this._name = nameString;
}
重构后
class Account...
constructor(number, type, interestRate) {
this._number = number;
this._type = type;
assert(interestRate === this._type.interestRate);
this._interestRate = interestRate;
}
get interestRate() {return this._interestRate;}
constructor(number, type) {
this._number = number;
this._type = type;
}
get interestRate() {return this._type.interestRate;}
三、搬移语句到函数
如果某些语句与一个函数放在一起更像一个整体,并且更有助于理解,那我就会毫不犹豫地将语句搬移到函数里去。如果它们与函数不像一个整体,但仍应与函数一起执 行,那我可以用提炼函数将语句和函数一并提炼出去
搬移语句到调用者
函数边界发生偏移的一个征兆是,以往在多个地方共用的行为,如今需要在某些调用点面前表现出不同的行为。于 是,我们得把表现不同的行为从函数里挪出,并搬移到其调用处。
重构前
function renderPerson(outStream, person) {
const result = [];
result.push(`<p>${person.name}</p>`);
result.push(renderPhoto(person.photo));
result.push(`<p>title: ${person.photo.title}</p>`);
result.push(emitPhotoData(person.photo));
return result.join("\n"); }
function photoDiv(p) {
return [
"<div>",
`<p>title: ${p.title}</p>`,
emitPhotoData(p),
"</div>",
].join("\n");
}
function emitPhotoData(aPhoto) {
const result = [];
result.push(`<p>location: ${aPhoto.location}</p>`);
result.push(`<p>date: ${aPhoto.date.toDateString()}</p>`);
return result.join("\n");
}
重构后
function renderPerson(outStream, person) {
const result = [];
result.push(`<p>${person.name}</p>`);
result.push(renderPhoto(person.photo));
result.push(emitPhotoData(person.photo));
return result.join("\n");
}
function photoDiv(aPhoto) {
return [
"<div>",
emitPhotoData(aPhoto),
"</div>",
].join("\n");
}
function emitPhotoData(aPhoto) {
return [
`<p>title: ${aPhoto.title}</p>`,
`<p>location: ${aPhoto.location}</p>`,
`<p>date: ${aPhoto.date.toDateString()}</p>`,
].join("\n");
}
四、以函数调用取代内联代码
一个命名良好的函数,本身就能极好地解释代码的用途,使读者不必了解其细节。函数同样有助于消除重复,因为同一段代码我不需要编写两次, 每次调用一下函数即可。
五、移动语句
1、让存在关联的东西一起出现,可以使代码更容易理解。 如果有几行代码取用了同一个数据结构,那么最好是让它们 在一起出现,而不是夹杂在取用其他数据结构的代码中间。
2、相比于仅仅把几行相关的代码移动到一起,将它们提炼到独立的函数往往能起到更好的抽象效果。
六、拆分循环
我们常常能见到一些身兼多职的循环,它们一次做了两三 件事情,不为别的,就因为这样可以只循环一次。但如果在一次循环中做了两件不同的事,那么每当需要修改循环时,都得同时理解这两件事情。如果能够将循环拆分,让 一个循环只做一件事情,那就能确保每次修改时只需要理解要修改的那块代码的行为就可以了。
性能问题(先进行重构,然后再进行性能优化)
七、以管道取代循环
八、移除无用代码