Refactoring第2版のサンプルコードをTypeScriptで実習する part.1 The First Step in Refactoring

[前回](https://qiita.com/ryo511/items/761210a2a6ed5689ef54)のStarting Pointから初めて、リファクタリングを開始します。

## The First Step in Refactoring

「リファクタリングを始める前に、テストが書いてある状態にしておくこと」と書いてるんですが、テストが載ってません。。。

テストランナーとか入れてもいいんですが、ひとまず最低限壊れていないことだけを保証したいので、assertを使って簡単なスクリプトを書いておきます。

```statement.spec.ts
import * as assert from "assert";
import { statement } from "./statement";

const invoice = {
  customer: "BigCo",
  performances: [
    {
      playID: "hamlet",
      audience: 55
    },
    {
      playID: "as-like",
      audience: 35
    },
    {
      playID: "othello",
      audience: 40
    }
  ]
};

const plays = {
  hamlet: { name: "Hamlet", type: "tragedy" },
  "as-like": { name: "As You Like It", type: "comedy" },
  othello: { name: "Othello", type: "tragedy" }
};

const actual = statement(invoice, plays);

const expected = `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
`;

assert.strictEqual(actual, expected);
```

これを `yarn ts-node statement.spec.ts` で実行します。テストがパスすれば、特に何も出力せず終了します。テストが失敗すると、エラーメッセージが表示されます。

npm scriptsで、`"test": "ts-node *.spec.ts"` のような定義を書き加えておくと便利かもしれません。

テストについては後々拡張していくことになりそうですが、開始地点としてはこんなものでよいでしょう。

## 関数の抽出

はじめに、以下のswitch文に注目します。

```statement.ts
...
    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}`);
    }
...
```

このコードは、それぞれのPerformanceについて、Playの種別に応じた料金を計算しています。ここはひとまとまりの関数として切り出せそうです。

```typescript
function amountFor(perf: Performance, play: Play): number {
  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}`);
  }
  return thisAmount;
}

...
    let thisAmount = amountFor(perf, play);
...
```

このように、機能の一部を関数として切り出すことを「関数の抽出(Extract Function)」と呼びます。WebStormなどのIDEでも、Refactoring > Extract Methodなどで簡単に行うことができます。

## 変数名の変更

次に、amountFor関数の中で、変数の名前をいくつか変えてみます。

- perf => aPerformance
- thisAmount => result

```typescript
function amountFor(aPerformance: Performance, play: Play): number {
  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;
}
```

- 関数の戻り値を保持する変数名は `result`
- 変数名が型を表すようにし、さらに冠詞をつける

これらのルールをそのまま適用する必要はないですが、一定のルールを設けることでコードの意図をわかりやすくすることができます。

次はamountFor関数のもう1つの引数playにメスを入れることになりますが、長くなるので続きは次回。