プロパティの初期値をnullにしたりしなかったりの使い分け

ふと意識した途端、クラスをnewしたときにプロパティへ**何を初期値として入れるべきなのか**が段々分からなくなってきました。

自分の中で考えられうる実装のバリエーションを分類した上で、どう使い分けるべきなのか現状の考えをメモします。

## A. 初期値は`null`だよ派

`null`とのユニオン型を定義し、コンストラクタ内で`null`をセットする実装。

- **メリット**
    - 意味のある値がセットされている状態か、そうでないかが区別できる
- **懸念**
    - プロパティを使用する際、`null`の場合の分岐も実装しなければならない

```ts
class MyEntity {
  public name: string | null;
  public num: number | null;
  public date: Date | null;
  public messages: string[] | null;
  public subEntity: SubEntity | null;

  constructor() {
    this.name = null;
    this.num = null;
    this.date = null;
    this.messages = null;
    this.subEntity = null;
  }
}
```

## B. 初期値は`undefined`だよ派

`undefined`とのユニオン型を定義し、意図的に`undefined`のままにしておく実装。

- **メリット**
    - 意味のある値がセットされている状態か、そうでないかが区別できる
- **懸念**
    - プロパティを使用する際、`undefined`の場合の分岐も実装しなければならない
    - 値が`undefined`のとき、本当に存在しないプロパティなのか、値がセットされていないだけなのかが分かりにくそう

```ts
class MyEntity {
  public name: string | undefined;
  public num: number | undefined;
  public date: Date | undefined;
  public messages: string[] | undefined;
  public subEntity: SubEntity | undefined;

  constructor() {
  }
}
```

## C. 初期値は空っぽい値だよ派

空っぽい値を明示的にコンストラクタ内でセットする実装。

- **メリット**
    - ユニオン型がいらないので、型定義がシンプル
    - `null`や`undefined`のケースを想定した分岐が不要
- **懸念**
    - 値がセットされていないのか、セットした値がたまたまデフォルト値と同じなのかが判別できない  
(配列が初期値のままなのか、セットした値が実際にゼロ件なのかなど)
    - 文字列型と配列以外の型では、本当の意味で「空っぽい値」を定義できない(特に日付型)

```ts
class MyEntity {
  public name: string;
  public num: number;
  public messages: string[];
  public subEntity: SubEntity;

  constructor() {
    this.name = '';
    this.num = 0; // 0が空っぽい値というのはかなり強引……
    this.messages = [];
    this.subEntity = new SubEntity();
  }
}
```

## D. 意味のあるデフォルト値をセットしちゃうよ派

明示的に意味のあるデフォルト値をコンストラクタ内でセットしてしまう実装。

- **メリット**
    - 型定義がシンプル
    - `null`や`undefined`の状態を想定せずに実装できる
    - 前提が整っていれば、初期状態と値がセットされた状態を区別できる
- **懸念**
    - 定義したデフォルト値が偶然セットした値と一致していまう可能性がある
    - デフォルト値のせいで無意味にメモリを消費するのでは
    - 設計の難易度が余計に増すのでは

```ts
class MyEntity {
  public name: string;
  public num: number;
  public date: Date;

  constructor() {
    this.name = 'not set'; // 初期値は'not set'だという仕様にしてしまう
    this.num = -9999; // 初期値は-9999だという仕様にしてしまう
    this.date = new Date(1889, 0); // 初期値は1889年1月1日だという仕様にしてしまう
  }
}
```

## E. 絶対にコンストラクタ引数からセットするよ派

コンストラクタの引数から値をとらせることを強制する実装。

- **メリット**
    - 型定義がシンプル
    - 初期値に悩む必要がない
    - `null`や`undefined`の状態を想定せずに実装できる
- **懸念**
    - 「まだ具体的な値は分からないけどとりあえずインスタンスを生成したい」という場合に使いにくい
    - 初期値をどうするのかという観点では、問題を一つ上のレイヤーへ先送りしただけ

```ts
class MyEntity {
  constructor(
    public name: string,
    public num: number,
    public date: Date,
    public messages: string[],
    public subEntity: SubEntity
  ) {
  }
}

const entity = new MyEntity('hoge', 1, new Date(), ['message1', 'message2'], new SubEntity());
```

## よく選ぶ実装パターンと選ばない実装パターン、その理由

### よく選ぶ実装パターン

実際は、プロパティの型に応じてよく使うパターンが分かれている印象です。
筆者の経験上妥当なことが多い実装パターンを挙げます。

- **文字列型**
    - 空文字列`''`を初期値する場合が多い。要件によっては`null`にすることもある
- **数値型**
    - `null`を初期値する場合が多い。要件によっては`0`にすることもある
- **日付型**
    - `null`を初期値とする場合が多い
- **配列型**
    - ゼロ件の配列`[]`をセットすることが多い。要件によっては`null`にすることもある
- **カスタム型**
    - その場でインスタンスをnewすることが多い。要件によっては`null`にすることもある

一般化はできていません。

要件に応じ、プロパティ値が初期状態かどうかを判定したい場合は初期値を`null`にする設計に寄せる。
そうでなければ空っぽい値をとる設計に寄せる、というのは言えるかもしれません。

```ts
// よく選ぶ実装パターンの例

class MyEntity {
  public name: string;
  public num: number | null;
  public date: Date | null;
  public messages: string[];
  public subEntity: SubEntity;

  constructor(param: Partial<MyEntity> = {}) {
    this.name = '';
    this.num = null;
    this.date = null;
    this.messages = [];
    this.subEntity = new SubEntity();

    Object.assign(this, param);
  }
}

// インスタンス生成時にパラメータオブジェクトから値を渡す使い方が好き
const entity = new MyEntity({
  name: 'Who bar',
  num: 0,
  date: new Date(1955, 1, 24),
  messages: ['who', 'bar']
});
```

### 滅多にやらないパターン

逆に、以下の3パターンを好んで選ぶことはほとんどありません。

- **B. 初期値は`undefined`だよ派**
    - 個人的には`undefined`を意図的に初期値とすることにあまりメリットが感じられません。`null`の方が意図が感じられます
- **D. 意味のあるデフォルト値をセットしちゃう派**
    - 「なぜそれがデフォルト値であるべきなのか」という根拠を考えるのが難しく、それなら`null`や空値の方がマシに感じます
    - たとえば入力フォームで最初からデフォルトの値を表示させておきたいなどのケースでは、初期化時のパラメータから拾うか、インスタンス生成後に値をセットすることで対応させる方が自然に感じます。
- **E. 絶対にコンストラクタ引数からセットするよ派**
    - インスタンス生成時に常に具体的な値が必要となるので不便な印象があります