Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

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

A. 初期値はnullだよ派

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

  • メリット
    • 意味のある値がセットされている状態か、そうでないかが区別できる
  • 懸念
    • プロパティを使用する際、nullの場合の分岐も実装しなければならない
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のとき、本当に存在しないプロパティなのか、値がセットされていないだけなのかが分かりにくそう
class MyEntity {
  public name: string | undefined;
  public num: number | undefined;
  public date: Date | undefined;
  public messages: string[] | undefined;
  public subEntity: SubEntity | undefined;

  constructor() {
  }
}

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

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

  • メリット
    • ユニオン型がいらないので、型定義がシンプル
    • nullundefinedのケースを想定した分岐が不要
  • 懸念
    • 値がセットされていないのか、セットした値がたまたまデフォルト値と同じなのかが判別できない
      (配列が初期値のままなのか、セットした値が実際にゼロ件なのかなど)
    • 文字列型と配列以外の型では、本当の意味で「空っぽい値」を定義できない(特に日付型)
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. 意味のあるデフォルト値をセットしちゃうよ派

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

  • メリット
    • 型定義がシンプル
    • nullundefinedの状態を想定せずに実装できる
    • 前提が整っていれば、初期状態と値がセットされた状態を区別できる
  • 懸念
    • 定義したデフォルト値が偶然セットした値と一致していまう可能性がある
    • デフォルト値のせいで無意味にメモリを消費するのでは
    • 設計の難易度が余計に増すのでは
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. 絶対にコンストラクタ引数からセットするよ派

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

  • メリット
    • 型定義がシンプル
    • 初期値に悩む必要がない
    • nullundefinedの状態を想定せずに実装できる
  • 懸念
    • 「まだ具体的な値は分からないけどとりあえずインスタンスを生成したい」という場合に使いにくい
    • 初期値をどうするのかという観点では、問題を一つ上のレイヤーへ先送りしただけ
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にする設計に寄せる。
そうでなければ空っぽい値をとる設計に寄せる、というのは言えるかもしれません。

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

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. 絶対にコンストラクタ引数からセットするよ派
    • インスタンス生成時に常に具体的な値が必要となるので不便な印象があります
y_hokkey
1988年生まれ。多摩美グラフィックデザイン学科を中退。デザイナーとしてグラフィック・エディトリアル・Web・UIのデザインを経験した後、Webフロントエンドエンジニアも経験。現在はITコンサルタントとして外資系SIerに在籍中。元LIG社員。※投稿内容は私個人の意見であり、所属企業・部門見解を代表するものではありません。
http://media-massage.net/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした