Refactoring第2版のサンプルコードをTypeScriptで実習する part.3 Split Loop and more

[前回](https://qiita.com/ryo511/items/cc12638c5fe919940378)の続き。

## Removing Total Volume Credits

次は、一時変数volumeCreditsの削除を目標にします。この値はループの中で作られているので、まずはSplit Loop（ループの分割）によって、volumeCreditsの計算だけを別のループに移します。

```typescript
  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の計算箇所の近くに移動しておきます。

```typescript
  let volumeCredits = 0;
  for (let perf of invoice.performances) {
    volumeCredits += volumeCreditsFor(perf);
  }
```

これによって、関連する処理が一カ所にまとまったので、関数を抽出します。

```typescript
  const totalVolumeCredits = (): number => {
    let volumeCredits = 0;
    for (let perf of invoice.performances) {
      volumeCredits += volumeCreditsFor(perf);
    }
    return volumeCredits;
  };
```

一時変数volumeCreditsは削除して、totalVolumeCredits関数の呼び出しに置き換えます。

```typescript
  result += `You earned ${totalVolumeCredits()} credits\n`;
```

次に、一時変数totalAmountも削除してしまいましょう。

```typescript
  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;
}
```

次は、計算処理と文字列のフォーマット処理を分割していきます。