前回の続き。
Removing Total Volume Credits
次は、一時変数volumeCreditsの削除を目標にします。この値はループの中で作られているので、まずはSplit Loop(ループの分割)によって、volumeCreditsの計算だけを別のループに移します。
for (let perf of invoice.performances) {
// print line for this order
result += ` ${playFor(perf).name}: ${usd(amountFor(perf))} (${
perf.audience
} seats)\n`;
totalAmount += amountFor(perf);
}
for (let perf of invoice.performances) {
volumeCredits += volumeCreditsFor(perf);
}
ついでに、volumeCreditsの初期化処理をSlide statements(文の移動)によって、volumeCreditsの計算箇所の近くに移動しておきます。
let volumeCredits = 0;
for (let perf of invoice.performances) {
volumeCredits += volumeCreditsFor(perf);
}
これによって、関連する処理が一カ所にまとまったので、関数を抽出します。
const totalVolumeCredits = (): number => {
let volumeCredits = 0;
for (let perf of invoice.performances) {
volumeCredits += volumeCreditsFor(perf);
}
return volumeCredits;
};
一時変数volumeCreditsは削除して、totalVolumeCredits関数の呼び出しに置き換えます。
result += `You earned ${totalVolumeCredits()} credits\n`;
次に、一時変数totalAmountも削除してしまいましょう。
const totalAmount = (): number => {
let result = 0;
for (let perf of invoice.performances) {
result += amountFor(perf);
}
return result;
};
totalAmountの計算処理を関数に抽出し、一時変数totalAmountをtotalAmount関数の呼び出しに置き換えます。
ここまでで現状の確認をしておきます。関数内関数が多いのは気になりますが、処理の流れはずっとシンプルになりました。なお、オリジナルのサンプルコードでは関数を使用するより後で定義していますが、↓のコードでは全て使用前に定義し、また、関数内関数であることがわかりやすいよう、function式ではなくアロー関数式で定義しています。
statement.ts
interface Performance {
playID: string;
audience: number;
}
interface Invoice {
customer: string;
performances: Array<Performance>;
}
interface Play {
name: string;
type: string;
}
function usd(aNumber: number): string {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2
}).format(aNumber / 100);
}
export function statement(
invoice: Invoice,
plays: { [playID: string]: Play }
): string {
const playFor = (aPerformance: Performance): Play =>
plays[aPerformance.playID];
const amountFor = (aPerformance: Performance): number => {
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 - 20);
}
result += 300 * aPerformance.audience;
break;
default:
throw new Error(`unknown type: ${playFor(aPerformance).type}`);
}
return result;
};
const volumeCreditsFor = (aPerformance: Performance): number => {
let result = 0;
result += Math.max(aPerformance.audience - 30, 0);
if (playFor(aPerformance).type === "comedy") {
result += Math.floor(aPerformance.audience / 5);
}
return result;
};
const totalVolumeCredits = (): number => {
let result = 0;
for (let perf of invoice.performances) {
result += volumeCreditsFor(perf);
}
return result;
};
const totalAmount = (): number => {
let result = 0;
for (let perf of invoice.performances) {
result += amountFor(perf);
}
return result;
};
let result = `Statement for ${invoice.customer}\n`;
for (let perf of invoice.performances) {
// print line for this order
result += ` ${playFor(perf).name}: ${usd(amountFor(perf))} (${
perf.audience
} seats)\n`;
}
result += `Amount owed is ${usd(totalAmount())}\n`;
result += `You earned ${totalVolumeCredits()} credits\n`;
return result;
}
次は、計算処理と文字列のフォーマット処理を分割していきます。