JavaScript/TypeScriptでnullを使うべきなのか

ブログ
JavaScript
TypeScript
約2600字

プロパティの初期値にnullをよく使うという話

TypeScriptでインスタンスを生成するときに、プロパティの初期値をどうするべきなのか……。最近ずっと気になっていた事柄だったので、考えをまとめてQiitaに投稿しました。

投稿の中で、自分がよく使う初期値の方針をまとめました。

  • string型: 空文字列かnull
  • numbe型: null0
  • Date型: null
  • 配列型: 空の配列かnull
  • カスタムクラス: インスタンスをnewするかnull

自分の傾向をまとめると、「型の種類を増やしたくないからできるだけnullは使いたくないけど、どうしようもないときはnullを初期値にする。undefinedは本当の未定義と見分けがつかないので初期値としては避ける。」ということになります。

nullundefinedにまつわる指摘と議論

この投稿の公開後に早速コメントがあり、nullはよく考えてから使ったほうが良いという指摘と参考記事を紹介されました。

提示されたGitHub Issueで話題になっていた問い掛けは「JavaScriptのユーザーランドではnullを廃止してundefinedに統一するべき」というものでした。

スレッドの中で明確な結論は出ていませんでしたが、nullの利用について肯定・否定の両派から意見が寄せられていました。

  • nullを肯定する意見
    • 標準APIやブラウザDOMがnullを利用している
    • nullundefinedは意味が違うので、使い分ければよい。nullにはundefinedより明示的に値が存在しないという意味があるので、その意味で使うべき
    • JSONではnullは表現できるが、undefiendは表現できない
  • nullに否定的な意見
    • APIのnullを使う仕様は変えられないが、ユーザーランドは別
    • そもそもnullundefinedが両方存在している現状が間違っているし、誰もちゃんと使い分けを知らない(だからundefinedに寄せるべき)
    • JSONでundefinedを扱う代替手段はどうとでもできる
  • 折衷案の意見
    • 結局は両方使うしかないが、nullを使う場合には意図を明確にするべき

他言語の経験がなくWebデザインの延長で開発をやるようになった自分にとって、この議論は興味深かったです。

僕にとってJavaScriptにnullundefinedが存在するという仕様は当然なことで、そこに疑問を持ったことはありませんでした。

その仕様には何か特別な理由があり、自分が知らないだけで上級者はちゃんと使い分けているものだと思っていたのですが……。

結局どうするべきなのか

似たような概念が2種類もあることでJavaScriptとTypeScriptの世界に無駄な混乱を招いているということは事実なようです。

確かに、使い分ける理由がないのであれば、寄せられる方に統一するべきでしょう。寄せるのであれば、nullよりもundefinedの方がより一般的なので適当です。

一方で、既存のAPIの仕様がある以上絶対にnullを無くすこともできません。

結局寄せ切ることはできませんが、nullの利用は明確な意図がない限りは最小限にするべき」という程度のことは言えるかもしれません。

  • 特に明確な意図がないのであればundefinedだけを使う
  • nullを使うのであれば利用意図を明確にする
  • 既存APIに渡す/帰ってくるnullは甘受する

プロパティ初期値としてnullを使うことの妥当性

さて、冒頭のプロパティ初期値の話題に戻ったとき、

型の種類を増やしたくないからできるだけnullは使いたくないけど、どうしようもないときはnullを初期値にする。undefinedは本当の未定義と見分けがつかないので初期値としては避ける。

という理由付けが妥当なのかをもう一度考えてみます。

プロパティが本当に未定義であるかの調べ方

仮に初期値としてundefinedを代入したときに、本当に存在しないプロパティなのかどうかはhasOwnPropertyで調べることができます。

しかしTypeScriptでundefinedを許容する型定義をすると、コンストラクタ内でプロパティへの代入をせずにクラスを実装できてしまいます。

クラスでの実装時には、どちらの型定義でもプロパティへの代入を省略できてしまう
オプショナル型とundefinedとのユニオン型は、オブジェクトとクラスで挙動が違った - Qiita

つまり、undefinedを許容する型定義ではプロパティの存在を保証できません。

それを保証したければ、コンストラクタ内でプロパティにundefinedを代入する運用が必要です。コンパイラが見てくれないのでちょっと面倒そうです。

class MyClass {
  public hoge: string | undefined;

  constructor() {
    // 書かなくてもコンパイルは通るが、省略するとhasOwnPropertyで区別できなくなる
    this.hoge = undefined;
  }
}

そもそも、プロパティ未定義と値がないことを区別したい場合があるのか

しかし、当初のnullを使う理由であった「本当にプロパティ未定義な状態と、値がない状態を区別できる」という機能が実際に必要な状況がどれだけあるでしょうか?

経験上、それが本当に必要だったシーンはこれまでありませんでした。 また、その機能が必要な状況を思いつくことも自分には難しいです。

オブジェクトのプロパティを走査するような機能には関係しそうですが、たとえばディープコピーするときにundefinedなプロパティが律儀に複製されても、そんなに嬉しい状況がなさそうです。

理由が思いつかないのであれば、やはりnullにこだわる意義はないかもしれません……。

あなたはnullを使いたいですか?

最後に、例のIssueを立てた人がTwitterで行っているnullundefinedにまつわるアンケートを転載しました。あなたはどちら派でしょうか?

補足情報

TypeScript公式リポジトリのWikiにnullを使ってはいけないという記述を見つけましたが、これはコンパイラの開発者向けガイドであり、TypeScriptそれ自体のためのガイドではありません。

STOP READING IMMEDIATELY THIS PAGE PROBABLY DOES NOT PERTAIN TO YOU These are Coding Guidelines for Contributors to TypeScript This is NOT a prescriptive guideline for the TypeScript community
Coding guidelines · microsoft/TypeScript Wiki

※投稿内容は私個人の意見であり、所属企業・部門見解を代表するものではありません。