BeAct Co., Ltd.

BLOG
社員ブログ

null や undefined とうまくお付き合いするためには

こんにちは! 今日もプログラミングを楽しんでいますか?

さてこんかいは、JavaScript をある程度経験した人でも迷ってしまう、 nullundefined についてお話していきたいと思います。

そもそもこの両者、大ざっぱに言うと「値がない」ということなのですが、初学者にとっては「どっちも同じようなものなのに、何が違うの?」と混乱するくらいの難関ではないでしょうか? というわけで、ここでそれぞれを整理してゆきましょう。


どっちも Nullish な値

Nullish (ぬりっしゅ) とは、 nullundefined をまとめて指す言葉です。

この言葉があるということは、実は JavaScript の仕様を策定した人たちも「これって同じようなものだよね」と考えていることをうかがわせますね。

では彼らは、何をもって「同じようなもの」としているのか、見てみましょう。

あいまいな等価比較で「同じ値」となる

JavaScript の比較演算子のうち、 == 、いわゆる等価演算子というものがあります。これは「あいまいな等価」(Loosely equal) とも呼ばれますが、これで nullundefined を比較すると、なんと true 、つまり「同じ値」と評価されます!

console.log(null == undefined); // "true"

「ヌル値合体演算子」の結果が同じ

?? 、いわゆるヌル値合体演算子という演算子は、左項に「値がない」場合、右項の値を評価する2項演算子です。

そして、この「値がない」とは、Nullish のことを指します。つまり、以下のコードは同じ結果となります!

console.log(null ?? 'この項で評価される');

console.log(undefined ?? 'この項で評価される');

// どちらも "この項で評価される" と表示

「オプショナルチェーン演算子」の結果が同じ

?. 、いわゆるオプショナルチェーン演算子とは、「オブジェクト(≒連想配列)」の特定のプロパティが「値がない」場合、エラーを出さずに undefined と評価する演算子です。

つまり、以下のコードは同じ結果となります!

const myObject = {
  name: null,
  birthDay: undefined,
};

// null にぶら下がるプロパティなんかないよ!
console.log(myObject.name?.value);

// undefined にぶら下がるプロパティなんかないよ!
console.log(myObject.birthDay?.value);

// そもそも id なんてプロパティはないよ!
console.log(myObject.id?.value);

// いずれもエラーを出さずに "undefined" と表示

では、そもそも何が違うの?

JavaScript の仕様が、ここまで「同じように使っていいよ」とお膳立てしてくれているのに、どうして nullundefined という「値がない」値どうしが区別されているのでしょうか? それぞれが作られた意図を見てみましょう。

null = プログラマ自身によってつけられた「値がない」ことを明示する目印

実は JavaScript は、みずから null を生み出すことは(原則として)ありません。 null は、プログラマが「わざわざ」変数に与える目印です。

/**
 * ステータスコード。
 * 
 * 0: 正常
 * 1: 実行不能エラー発生
 * 2: 不変条件違反エラー発生
 * 400~: 通信エラー時のステータスコード
 * null: その他のエラー
 * 
 * @type { number | null }
 */
let statusCode = 0;

// 別の箇所
if (result.success === true) {
  if (result.data == undefined) {
    // 通信には成功したが、値が取得できなければ「その他のエラー」。
    statusCode = null;

    return;
  }
}

0null で、コード上の意味が異なっています。もしこれが区別できないと、不具合が起きたときにプログラマが変数 statusCode の内容を見ても、正常に動いているのか、エラーが起こっているのかが判別できません。 null は、プログラマという人間にとっての利便性のために、あえて変数に与える値として作られました。

またこの目印は「その変数は、参照を持たない」という意味でも使われ、これをヌル参照と呼びます。この概念は、ALGOL 60の実装者として知られるアントニー・ホーアによって考案されました。

undefined = コンピュータが「そんなの知らないよ」と、人間を叱る値

一方で、 undefined はその名の通り「(プログラム上)未定義(だから、処理を進められないよ)」という「未定義エラー」を表現するためのお叱りです。

let favorite = 'apple';

// `favorite` を `faborite` と書き間違えてしまった!
console.log(`わたしは ${faborite} が食べたい`);

このコードを実行すると、

Error: faborite is not defined

というお叱りが飛んできます。コンピュータはあまりにも素直なので、一字でも違うと別のものだと認識します。そのため、変数名の打ち間違い等に対し「そんな変数はない(未定義だ)よ」と叱ってくれる値として作られました。

その他の違い

typeof 演算子による評価が異なります。これは上記の違いを実装した結果です。

console.log(typeof null); // "object"

console.log(typeof undefined); // "undefined"

ホーアの「十億ドルの過ち」

いま述べたとおり、 nullundefined はそれぞれ意図が異なります。どちらの理屈も、一見もっともらしく聞こえますね。

しかし、ヌル参照を考案したホーアは、2009年に「ヌル参照は、十億ドルの過ち (a “billion-dollar mistake”) であった」と発言しました。以下はその原文の翻訳(翻訳、訳注はブログ執筆者による)です。

私は、1965年にヌル参照を発明してしまったことを、十億ドルの過ちと呼んでいる。当時、私はオブジェクト指向言語(ALGOL W)における参照のための、初の包括的な型システムを設計していた。参照が全て完全に安全に使用でき、コンパイラによって自動的にチェックが行われるようにすることが目的だった。しかし、実装があまりにも簡単だったという理由だけで、ヌル参照を導入する誘惑に勝てなかった。これにより、(訳注:現在に至るまで、世界中のプログラミング業において)数えきれないほどのエラー、脆弱性、システムクラッシュが生まれ、過去40年間でおそらく十億ドルもの苦痛と損害をもたらしてしまった。

出典:Wikipedia “Null pointer – History”(筆者訳)

その反省からか、 C# や Flutter、Kotlin、TypeScript など、現在のプログラミング言語の多くは、「Null安全性」を備えています。これは「その変数は null となりうるかどうか(= Nullable であるか)」を言語仕様レベルで決められる性質のことです。これは言い換えると、「 null はそれだけ危険視されている」ということでもあります。

null や undefined とうまくお付き合いするためには

JavaScript において、この現状から目を背けず、彼らとうまくお付き合いするにはどうすればいいか?

ずばり、「自分のコードでは、 null を書かないです。

……それじゃ付き合ってることにならないじゃないか、という意見はごもっともです。しかしこのプラクティス(心がけ、慣習)は、 null そのものを否定しているわけではありません。例えば、以下の状況では null を書かなければならないでしょう。

JavaScript の関数が、 null を返す場合

以下の JavaScript の関数やメソッド、プロパティは、 null を返す・設定される可能性があります。

  • 文字列.match(正規表現) で、候補が見つからなかった場合。
  • DOMエレメント.querySelector() などで、候補が見つからなかった場合。
  • document.textContent の値。
  • スクロールスナップのイベントが「インライン方向で」発生した際に渡される、 SnapEvent インスタンスの snapTargetBlock プロパティ。
  • ……などなど。

これらは JavaScript API の仕様なので、「 null を返すものなんだ」と割り切るしかありません。皆さんがコードを記述するときは、そこだけ「 null かどうか」を判定させるとよいでしょう。

第三者が提供するモジュールが、 null を返す場合

これも同じ理由です。自分で作ったものではない以上、自己責任で改造を施す以外にコントロールする手段はありません(もちろんお勧めしません!)。素直に仕様に従うか、配布者がイシューを受け付けている場合には(ダメ元で)イシューを投稿してみましょう。

まとめ:自分でできるプラクティス

最後に、自分のコードで null を避けるためのプラクティスを、いくつか挙げてみます。

  • その変数に「値がない」かどうかの判定は、variable == undefined という「あいまいな等価」を使う。
    • JavaScript API や外部モジュールから null が返ってきたとしても、これなら十分に判定できる。
  • ??, ?. を積極的に使い、「値がない」を undefined に一本化する。
  • ガベージコレクトのための「参照の切り離し」には、ref = null ではなく、ref = undefined を使う。
    • JavaScript の参照存在判定はマーク・アンド・スイープ法であり、参照を切り離して「未到達オブジェクト」にするのなら undefined の代入でよい。
  • undefined に、コード上の意味を持たせない。
    • これを破ると、「これはバグによって値が入らなかったのか? それとも正しく動作したうえで、何かの意味を示しているのか?」が紛らわしくなり、原因の特定が困難な不具合の温床となる。

皆さんのコードが、より見通しが良くなることを、祈っています。

それでは、良きコーディングライフを!