初心者の頃に知りたかった、TypeScript の良いコードと悪いコード

# 概要

TypeScriptを学習していき、初心者からもう一歩進んで中級者になるために、私が参考になったものを紹介します。
それを「良いコード」と「悪いコード」という形式で書いていきます。「こういう書き方に直せるんだ」「こう書いたほうがいいんだ」というのが直感的にわかりやすいと思います。しばらく経ってからまた見返すものとしても良いです。

元は以下で公開されている「Clean Code」の記事です。
https://msakamaki.github.io/clean-code-typescript/
私の記事は、あまりにも基本的なことは除外したり、逆に難しいところは解説を加えたものです。
時間のある人は元の記事の全文に目を通しても良いと思います。

# 他に参考にしたもの

どのサイトも素晴らしいので、TypeScript を学習するならオススメです。

* [TypeScript Deep Dive 日本語版](https://typescript-jp.gitbook.io/deep-dive)
* [サバイバルTypeScript](https://typescriptbook.jp/)

# フォーマット

### タイプスクリプトエイリアスを使用する

tsconfig.json の compilerOptions セクションで、path と baseUrl プロパティを定義するとより綺麗なimportを書くことができる。
これにより、import時に長い相対パスの使用を避けることができる。

Bad:

```typescript
import { UserService } from '../../../services/UserService';
```

Good:

```typescript
import { UserService } from '@services/UserService';
```
tsconfig.json
```json
...
  "compilerOptions": {
    ...
    "baseUrl": "src",
    "paths": {
      "@services": ["services/*"]
    }
    ...
  }
...
```

# 関数

### 引数

* 関数における引数は2つ以下が理想。それ以上は関数がやりすぎている可能性がある。
* どうしても必要ならオブジェクトリテラルの利用を検討する。例を以下に示す。


Bad:
```typescript
function createMenu(title: string, body: string, buttonText: string, cancellable: boolean) {
  // ...
}
createMenu('Foo', 'Bar', 'Baz', true);
```

Good:
```typescript
function createMenu(options: { title: string, body: string, buttonText: string, cancellable: boolean }) {
  // ...
}

createMenu({
  title: 'Foo',
  body: 'Bar',
  buttonText: 'Baz',
  cancellable: true
});
```

さらにGood:
```typescript
type MenuOptions = { title: string, body: string, buttonText: string, cancellable: boolean };

function createMenu(options: MenuOptions) {
  // ...
}

createMenu({
  title: 'Foo',
  body: 'Bar',
  buttonText: 'Baz',
  cancellable: true
});
```

### デフォルト値の指定

Object.assign や 分割代入を使ってデフォルトオブジェクトを設定するとよい。

Bad:
```typescript
type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean };

function createMenu(config: MenuConfig) {
  config.title = config.title || 'Foo';
  config.body = config.body || 'Bar';
  config.buttonText = config.buttonText || 'Baz';
  config.cancellable = config.cancellable !== undefined ? config.cancellable : true;

  // ...
}

createMenu({ body: 'Bar' });
```

Good:
```typescript
type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean };

function createMenu(config: MenuConfig) {
  const menuConfig = Object.assign({
    title: 'Foo',
    body: 'Bar',
    buttonText: 'Baz',
    cancellable: true
  }, config);

  // ...
}

createMenu({ body: 'Bar' });
```

もしくはこちらもGood:
```typescript
type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean };

function createMenu({ title = 'Foo', body = 'Bar', buttonText = 'Baz', cancellable = true }: MenuConfig) {
  // ...
}

createMenu({ body: 'Bar' });
```


### 命令型プログラミングよりも関数型プログラミングを好む

Bad:
```typescript
const contributions = [
  {
    name: 'Uncle Bobby',
    linesOfCode: 500
  }, {
    name: 'Suzie Q',
    linesOfCode: 1500
  }, {
    name: 'Jimmy Gosling',
    linesOfCode: 150
  }, {
    name: 'Gracie Hopper',
    linesOfCode: 1000
  }
];

let totalOutput = 0;

// 以下のように for 文でグルグル回しがちだが...
for (let i = 0; i < contributions.length; i++) {
  totalOutput += contributions[i].linesOfCode;
}
```

Good:
```typescript
const contributions = [
  {
    name: 'Uncle Bobby',
    linesOfCode: 500
  }, {
    name: 'Suzie Q',
    linesOfCode: 1500
  }, {
    name: 'Jimmy Gosling',
    linesOfCode: 150
  }, {
    name: 'Gracie Hopper',
    linesOfCode: 1000
  }
];

// reduce で書ける
const totalOutput = contributions
  .reduce((totalLines, output) => totalLines + output.linesOfCode, 0);
```

この例ではやや難しいと言われる reduce が使われているが、map, filter, find などの基本的な関数が使いこなせるとグッと TypeScript が上達する。

reduce やその他関数の解説は以下の記事がわかりやすかった。
https://zenn.dev/nekoniki/articles/07c09eb6811c85a753de

### イテレーターとジェネレーター

少し難しいが、意外と使いどころが多いのがこのイテレーターとジェネレーターの概念。ストリーミングのようなコレクションを扱うときは、これが使えないか考えてみてほしい。
`function*` と `yield(イールド)` という構文を使い、ジェネレーター関数を作る。このジェネレーター関数を呼び出すと、ジェネレーターオブジェクトが返るのだが、ジェネレーターオブジェクトはイテレーターインターフェースを実装している。つまり、 `next` `return` `throw` 関数が使えるのである。

例を見てみよう。

```typescript
function* idMaker(){
  let index = 0;
  while(index < 3)
    yield index++;
}

// ジェネレーター関数を呼び出し、ジェネレーターオブジェクトを得る
let gen = idMaker();

// next 関数を使って「0」「1」「2」という結果を順番に得ることができる
console.log(gen.next()); // { value: 0, done: false }
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
// イテレーターが終了した場合には { done: true } という結果が得られる
console.log(gen.next()); // { done: true }
```

`function* idMaker` 関数には `return` がない変わりに `yield` が使われているのがポイント。 `yield` を使うと「遅延評価される」のだが、これは誤解を恐れず言えば「その都度return」されるようなイメージである。

また、イテレーターなので、for-of 構文を使った組み込みの反復処理もサポートしている。
これを例にした悪いコード、良いコードを見てみよう。

Bad:
```typescript
// フィボナッチ数を出力する関数
function fibonacci(n: number): number[] {
  if (n === 1) return [0];
  if (n === 2) return [0, 1];

  // 普通にやると return する配列の全数が必要だが...
  const items: number[] = [0, 1];
  while (items.length < n) {
    items.push(items[items.length - 2] + items[items.length - 1]);
  }

  return items;
}

function print(n: number) {
  fibonacci(n).forEach(fib => console.log(fib));
}

// フィボナッチ数の最初の10個をprintする
print(10);
```

Good:
```typescript
// フィボナッチ数の無限のストリームを生成する関数
function* fibonacci(): IterableIterator<number> {
  let [a, b] = [0, 1];

  while (true) {
    // ジェネレータを使うことで、全数の配列を保持しなくてよくなる
    yield a;
    [a, b] = [b, a + b];
  }
}

function print(n: number) {
  let i = 0;
  // for-of でループ処理できる
  for (const fib of fibonacci()) {
    if (i++ === n) break;  
    console.log(fib);
  }  
}

// フィボナッチ数の最初の10個をprintする
print(10);
```

これだけの例だとあまりピンとこないかもしれない。
詳しくは以下の解説記事を読んでほしい。
https://typescript-jp.gitbook.io/deep-dive/future-javascript/generators


# オブジェクトとデータ構造

### getter と setter を使う

TypeScriptは getter / setter 構文をサポートしている。 
getter と setter を使って振る舞いをカプセル化し、オブジェクトにアクセスするほうが優れている可能性がある。 理由としては以下。

* もしオブジェクトのプロパティを取得する以上のことをしてる場合、コード内のすべてのアクセサを調べて変更する必要がない。
* set を使うとバリデーションが追加できる。
* 内部をカプセル化できる。
* 値を取得や設定する時にログやエラー処理を追加するのが容易になる。
* オブジェクトのプロパティを遅延ロードすることができるようになる。例えばサーバから値を取得する時など。

例を見てみよう。

Bad:
```typescript
type BankAccount = {
  balance: number;
  // ...
}

const value = 100;
const account: BankAccount = {
  balance: 0,
  // ...
};

if (value < 0) {
  throw new Error('Cannot set negative balance.');
}

// アカウントオブジェクトの balance に値を入れるコード
account.balance = value;
```

Good:
```typescript
// これで BankAccount はカプセル化された
class BankAccount {
  private accountBalance: number = 0;

  get balance(): number {
    return this.accountBalance;
  }

  set balance(value: number) {
    // setter の中でバリデーションをすることができる
    if (value < 0) {
      throw new Error('Cannot set negative balance.');
    }

    this.accountBalance = value;
  }

  // ...
}

const account = new BankAccount();
account.balance = 100;
```

ある日仕様が変更されて、追加のバリデーションが必要になった場合にも `setter` の実装だけを変更すればよく、すべての依存したコードを変更する必要はなくなった。

### タイプ vs インターフェース

TypeScript には `type` と `interface` があり、どちらも近いことができるためどう使うかを迷うことがある。
例えば以下を見てほしい。

```typescript
interface Animal {
  name: string;
  bark(): string;
}
type Animal = {
  name: string;
  bark(): string;
};
```

どちらも「動物」を表現しているものだが、どちらが適しているのか。
まず、`union` や `intersection` が必要な場合は `type` を使用する。 `extends` や `implements` がほしいときには `interface` を使う。と覚えておくと良いだろう。

これを使った悪いコード、良いコードを見てみよう。

Bad:
```typescript
interface EmailConfig {
  // ...
}

interface DbConfig {
  // ...
}

interface Config {
  // ...
}

//...

type Shape = {
  // ...
}
```

Good:
```typescript
type EmailConfig = {
  // ...
}

type DbConfig = {
  // ...
}

type Config  = EmailConfig | DbConfig;

// ...

interface Shape {
  // ...
}

class Circle implements Shape {
  // ...
}

class Square implements Shape {
  // ...
}
```

### インヘリタンス(継承)よりコンポジション(合成集約)を好む

可能ならば継承よりも合成集約を優先するべき。もし継承を使いたくなったら、まず合成集約でやれないか考えてみるとよい。

では逆にどういうときに継承を使うべきか？

* 継承が「has-a」ではなくて「is-a」を表している場合(例：人間は動物である)
* 基底クラスからコードを再利用できる(例：人は動物のように動くことができる)
* 基底クラスを変更することで、派生クラスを全体的に変更したい(例：全ての動物の移動中の消費カロリーを変更する、など)

以下は継承の誤った使い方。

Bad:

```typescript
class Employee {
  constructor(
    private readonly name: string,
    private readonly email: string) {
  }

  // ...
}

// よくない。なぜなら、従業員(Employee)は税情報を持っている。
// しかし、従業員税情報(EmployeeTaxData)は従業員ではない。
class EmployeeTaxData extends Employee {
  constructor(
    name: string,
    email: string,
    private readonly ssn: string,
    private readonly salary: number) {
    super(name, email);
  }

  // ...
}
```

Good:
```typescript
class Employee {
  private taxData: EmployeeTaxData;

  constructor(
    private readonly name: string,
    private readonly email: string) {
  }
  // 従業員は税情報を持つ、「has-a」の関係
  setTaxData(ssn: string, salary: number): Employee {
    this.taxData = new EmployeeTaxData(ssn, salary);
    return this;
  }

  // ...
}

class EmployeeTaxData {
  constructor(
    public readonly ssn: string,
    public readonly salary: number) {
  }

  // ...
}
```


### メソッドチェーンを利用すること

Bad:
```typescript
class QueryBuilder {
  private collection: string;
  private pageNumber: number = 1;
  private itemsPerPage: number = 100;
  private orderByFields: string[] = [];

  from(collection: string): void {
    this.collection = collection;
  }

  page(number: number, itemsPerPage: number = 100): void {
    this.pageNumber = number;
    this.itemsPerPage = itemsPerPage;
  }

  orderBy(...fields: string[]): void {
    this.orderByFields = fields;
  }

  build(): Query {
    // ...
  }
}

// ...

const queryBuilder = new QueryBuilder();
queryBuilder.from('users');
queryBuilder.page(1, 100);
queryBuilder.orderBy('firstName', 'lastName');

const query = queryBuilder.build();
```

Good:
```typescript
class QueryBuilder {
  private collection: string;
  private pageNumber: number = 1;
  private itemsPerPage: number = 100;
  private orderByFields: string[] = [];

  from(collection: string): this {
    this.collection = collection;
    return this;
  }

  page(number: number, itemsPerPage: number = 100): this {
    this.pageNumber = number;
    this.itemsPerPage = itemsPerPage;
    return this;
  }

  orderBy(...fields: string[]): this {
    this.orderByFields = fields;
    return this;
  }

  build(): Query {
    // ...
  }
}

// ...

const query = new QueryBuilder()
  .from('users')
  .page(1, 100)
  .orderBy('firstName', 'lastName')
  .build();
```

# エラー処理

### Throw や Reject の時に Error を使う

TypeScript は任意のオブジェクトを throw できる。 Promiseの場合は reject することができる。

Error 型と throw 構文を使うようにする。エラーはより上位のコードで catch され適切に処理したい。その時に文字列でメッセージを出すのはとても混乱を招き、デバッグを厄介にする。同じ理由で Error 型の reject を行うべき。

Bad:
```typescript
function calculateTotal(items: Item[]): number {
  throw 'Not implemented.';
}

function get(): Promise<Item[]> {
  return Promise.reject('Not implemented.');
}
```

Good:
```typescript
// Good
function calculateTotal(items: Item[]): number {
  throw new Error('Not implemented.');
}

// Promise を使っている場合の Good 例
function get(): Promise<Item[]> {
  return Promise.reject(new Error('Not implemented.'));
}

// もしくはこれでも良い

async function get(): Promise<Item[]> {
  throw new Error('Not implemented.');
}
```

Error 型を使う利点は、`try` `catch` `finally` 構文がサポートされていること、暗黙的に stack プロパティを持つためデバッグにしやすいからだ。 `throw` 構文を使わずに常にカスタムエラーオブジェクトを返すという方法もある。 TypeScriptはそれを更に簡単する。 次の例を参考に。

```typescript
type Result<R> = { isError: false, value: R };
type Failure<E> = { isError: true, error: E };
type Failable<R, E> = Result<R> | Failure<E>;

function calculateTotal(items: Item[]): Failable<number, 'empty'> {
  if (items.length === 0) {
    return { isError: true, error: 'empty' };
  }

  // ...
  return { isError: false, value: 42 };
}
```


# 並行性

### コールバックではなく Promise, async / await を使う

コールバックが無数にも繋がり、コールバック地獄と呼ばれるコードが以下である。

Bad: 
```typescript
import { get } from 'request';
import { writeFile } from 'fs';

function downloadPage(url: string, saveTo: string, callback: (error: Error, content?: string) => void) {
  get(url, (error, response) => {
    if (error) {
      callback(error);
    } else {
      writeFile(saveTo, response.body, (error) => {
        if (error) {
          callback(error);
        } else {
          callback(null, response.body);
        }
      });
    }
  });
}

downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', 'article.html', (error, content) => {
  if (error) {
    console.error(error);
  } else {
    console.log(content);
  }
});
```

これをまずは Promise で書き直してみる。

Good:
```typescript
import { get } from 'request';
import { writeFile } from 'fs';
import { promisify } from 'util';

const write = promisify(writeFile);

function downloadPage(url: string, saveTo: string): Promise<string> {
  return get(url)
    .then(response => write(saveTo, response));
}

downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', 'article.html')
  .then(content => console.log(content))
  .catch(error => console.error(error));  
```

async / await を使うとさらに Good:
```typescript
import { get } from 'request';
import { writeFile } from 'fs';
import { promisify } from 'util';

const write = promisify(writeFile);

function downloadPage(url: string, saveTo: string): Promise<string> {
  return get(url)
    .then(response => write(saveTo, response));
}

try {
  const content = await downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', 'article.html');
  console.log(content);
} catch (error) {
  console.error(error)); 
}
```

# 終わりに

いかがでしたでしょうか。
「もう全部知っているよ」という強者もいるかもしれません。
自分はよく忘れてしまうので定期的に見返しています。

他にも「こういう章があってもいいんじゃないか」「こういう書き方もできるんじゃないか」という意見があればお願いします。
それではまた。