LoginSignup
1
1

More than 5 years have passed since last update.

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

Posted at

前回の続き。

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

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1