重构 改善既有代码的设计 第二版 - A First Example
THE STARTING POINT
一家戏剧公司,演员会参加表演。戏剧分为喜剧和悲剧。公司根据观众的规模和戏剧的类型收费。除了账单,还有volume credits,未来的折扣。
plays.json的数据是这样的:
{
"hamlet": {
"name": "Hamlet", "type": "tragedy"},
"aslike": {
"name": "As You Like It", "type": "comedy"},
"othello": {
"name": "Othello", "type": "tragedy"}
}
账单,invoices.json文件是这样的:
[
{
"customer": "BigCo",
"performances": [
{
"playID": "hamlet",
"audience": 55
},
{
"playID": "aslike",
"audience": 35
},
{
"playID": "othello",
"audience": 40
}
]
}
]
打印账单的函数:
function statement (invoice, plays) {
let totalAmount = 0;
let volumeCredits = 0;
let result = `Statement for ${
invoice.customer}\n`;
const format = new Intl.NumberFormat("enUS",
{
style: "currency", currency: "USD",
minimumFractionDigits: 2 }).format;
for (let perf of invoice.performances) {
const play = plays[perf.playID];
let thisAmount = 0;
switch (play.type) {
case "tragedy":
thisAmount = 40000;
if (perf.audience > 30) {
thisAmount += 1000 * (perf.audience 30);
}
break;
case "comedy":
thisAmount = 30000;
if (perf.audience > 20) {
thisAmount += 10000 + 500 * (perf.audience 20);
}
thisAmount += 300 * perf.audience;
break;
default:
throw new Error(`unknown type: ${
play.type}`);
}
// add volume credits
volumeCredits += Math.max(perf.audience 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" === play.type) volumeCredits += Math.floor(perf.audience / 5);
// print line for this order
result += ` ${
play.name}: ${
format(thisAmount/100)} (${
perf.audience} seats);
totalAmount += thisAmount;
}
result += `Amount owed is ${
format(totalAmount/100)}\n`;
result += `You earned ${
volumeCredits} credits\n`;
return result;
}
输出是这样的:
Statement for BigCo
Hamlet: $650.00 (55 seats)
As You Like It: $580.00 (35 seats)
Othello: $500.00 (40 seats)
Amount owed is $1,730.00
You earned 47 credits
DECOMPOSING THE STATEMENT FUNCTION
先从switch开始重构(Extract Function),其中,把thisAmount重命名为result,把perf重命名为aPerformance。
function amountFor(aPerformance, play) {
let result = 0;
switch (play.type) {
case "tragedy":
result = 40000;
if (aPerformance.audience > 30) {
result += 1000 * (aPerformance.audience 30);
}
break;
case "comedy":
result = 30000;
if (aPerformance.audience > 20) {
result += 10000 + 500 * (aPerformance.audience 20);
}
result += 300 * aPerformance.audience;
break;
default:
throw new Error(`unknown type: ${
play.type}`);
}
return result;
}
这样,statement修改为:
let thisAmount = amountFor(perf, play);
Removing the play Variable
play可以在amountFor内重新计算。
重构长函数的时候,喜欢摆脱像play这样的变量。因为临时变量会创建很多本地范围内的名称,这些名称使得提取变得负责。这叫Replace Temp with Query。
function playFor(aPerformance) {
return plays[aPerformance.playID];
}
statement修改成:
const play = playFor(perf);
然后,去掉play变量:
for (let perf of invoice.performances) {
let thisAmount = amountFor(perf, playFor(perf));
// add volume credits
volumeCredits += Math.max(perf.audience 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" === playFor(perf).type) volumeCredits += Math.floor(perf.audience / 5);
// print line for this order
result += ` ${
playFor(perf).name}: ${
format(thisAmount/100)} (${
perf.audience} seats);
totalAmount += thisAmount;
}
然后可以Change Function Declaration,删除play参数:
function amountFor(aPerformance) {
let result = 0;
switch (playFor(aPerformance).type) {
case "tragedy":
result = 40000;
if (aPerformance.audience > 30) {
result += 1000 * (aPerformance.audience 30);
}
break;
case "comedy":
result = 30000;
if (aPerformance.audience > 20) {
result += 10000 + 500 * (aPerformance.audience