Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Let's Define Undefined

2026年09月22日に「Dev.to」が公開したITニュース「Let's Define Undefined」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

JavaScriptで`undefined`を値がない、空、失敗など多様な状況で使うと意味が曖昧になる。代わりに、型に応じた空のデフォルト値(`''`, `[]`, `0`など)を設定することで、値が「空」なのか「未定義」なのかを明確に区別できる。これによりコードの意図が明確になり、`undefined`は本当に値が提供されていない場合に限定して使うべきだ。

出典: Let's Define Undefined | Dev.to公開日:

ITニュース解説

JavaScriptの開発では、値が存在しない、空である、処理に失敗した、あるいはまだその意味がはっきりしないといった様々な状況で、安易にundefinedという値を使う習慣がしばしば見られる。undefinedはどこでも利用でき、JavaScriptの仕組み上「偽」の値(falsy)として扱われるため、多くの場合は便利に思える。しかし、一つのundefinedという値でこれらの多様な状態全てを表現しようとすると、かえってその値が持つべき意味が曖昧になり、後続の処理で問題が生じることがある。たとえば、文字列を期待していた箇所でundefinedが渡されると、文字列の操作(例えばtrim())がエラーになる場合がある。

このような状況を解決するため、「Resilient」というアプローチでは、値の不在をより厳密に区別して扱う方法を提案している。この考え方では、もし契約(ある変数がどのような値を持つべきかという取り決め)が実行可能な「空の形式」を持っているならば、その空の形式を契約内で維持すべきであると考える。一方で、undefinedは「契約上の値がそもそも提供されなかった、または生成されなかった」という、より限定された意味のために予約される。この明確な区別により、値がないこと、不確実であること、そして「偽」であることといった異なる状態が、それぞれ別の表現を持つことができるようになる。

このアプローチの中心にあるのは「デフォルト値」の活用だ。JavaScriptの関数定義などで使われるデフォルト値は、単なる便宜的な設定ではなく、値の不在時にその契約がどのように振る舞うかを定義する実行可能な「合意」としての意味を持つ。例えば、const readTitle = ({ title = '' } = {}) => title.trim();というコードでは、titleというプロパティが省略されたりundefinedであったりした場合に、デフォルト値として空の文字列''が設定される。これにより、その後のtrim()という文字列操作が常に問題なく実行できることが保証される。これは、テキストがない状態を「文字のない文字列」として扱うという関数側の明確な合意を示すものだ。

Resilientでは、このようなデフォルト値を「偽化可能なデフォルト値(Falsifiable Defaults)」と呼ぶ。関数の引数における分割代入のシグネチャ(引数の定義部分)は、関数が受け取るべき値の「形状」を宣言し、そのデフォルト値は、その形状内で値の不在を表すために使用できる「利用可能な値」を宣言する。例えば、const renderArticle = ({ title = '', tags = [], metadata = {} } = {}) => ...という関数定義では、titleは文字列、tagsは配列、metadataはオブジェクトという合意が確立される。対応するプロパティが渡されなかったりundefinedであったりしても、これらのデフォルト値によって型が維持されるため、その後の処理や戻り値の型チェックも、それぞれの型に合意されたまま実行できる。これは、コードを静的に解析するツールにとっても、値の流れを追跡しやすくなるという利点がある。

この「偽化可能なデフォルト値」は、0(数値)、false(真偽値)、''(空文字列)、[](空配列)、{}(空オブジェクト)のように、それ自体がJavaScriptの仕組み上「偽」として評価される(falsy)が、それぞれの型としては完全に有効な値である。例えば、!0はtrueだが、0は依然として数値であり、数値としての操作が可能だ。重要なのは、値が「不在」の状態であっても、その値が属する型ファミリー(数値、文字列、配列など)を放棄しないことである。これにより、その値を利用する側(消費者)は、その型が持つ操作(例えば、文字列ならtrim()、配列ならmap())を、値が提供されている場合と同様に利用できる。

ただし、JavaScriptでは空の配列[]や空のオブジェクト{}は「真」と評価される(truthy)という特殊性があるため、それらが「空である」ことを確認するには、!items.length(配列の要素がないか)や!Object.keys(record).length(オブジェクトにプロパティがないか)のように、より具体的なテストが必要になる。このようなテストは、hasArrayContentやhasContentのようなヘルパー関数を使って明示的に行うことで、「コンテンツが存在するか」という特定の条件を明確に確認できる。

このように、契約が独自の偽化可能な状態を表現できるようになったとき、undefinedはもはや「あらゆる契約の普遍的な否定メンバー」として使われる必要がなくなる。undefinedには、より狭く、より有用な意味が与えられる。それは「契約上の値が提供されていない、または生成されていない」という事実だけを伝えるものとなる。例えば、count = 0やtitle = ''といった値は、それぞれ数値や文字列としての値が確立されていることを示すが、let func;のように変数を宣言しただけで値が割り当てられていない場合は、まだ呼び出し可能な値が確立されていないことをundefinedが表す。これにより、偽化可能な値とundefined、未知の状態と矛盾といった、これまで混同されがちだった異なる状態が明確に区別される。

特に「関数」の扱いではこの区別が重要になる。JavaScriptには「偽である呼び出し可能な関数」という概念は存在しない。たとえfunc: () => {}のように何も処理を行わない空の関数であっても、それは「実装が存在する」という明確な主張であり、呼び出し可能で「真」と評価される。一方で、let func;のように関数が割り当てられていない場合は、「呼び出し可能な値が供給されていない」ことをundefinedが表す。関数においては、空文字列や空配列のように、その型のデフォルト値(偽値)で「不在」を表現することが難しいため、undefinedが「実装が存在しない」ことを明確に伝える役割を担うのだ。

このような状況では、値を受け取る側(コンシューマー)が、その値をどう扱うかを決定する責任を負う。関数が呼び出し可能な値を必ず必要とする場合もあれば、その不在を許容する場合もある。後者の場合、コンシューマーはif (!isFunction(func)) return;のようにガード句を使って、関数が提供されていない場合の処理を決定する責任がある。これにより、値を提供する側(プロデューサー)は「合意」を提示し、コンシューマーは自身のスコープ内でその「解釈」を決定するという役割分担が明確になる。

ECMAScriptの仕様では、undefinedは「変数が値に割り当てられていないときに使用されるプリミティブ値」と定義されている。Resilientのアプローチは、この言語仕様のメカニズムを再定義するのではなく、アプリケーションの中でこのundefinedという表現がどのような意味を持つべきか、という規律(ルール)を提案している。たとえば、let func;は実装が供給されていない状態を表す。これに対し、func = undefined;と明示的にundefinedを代入した場合は、すでに存在していた値が意図的にクリアされたり、無効化されたり、取り消されたりした可能性を示唆する。Resilientでは、undefinedは「供給されなかったもの」や「生成されなかったもの」から自然に生じる場合に最も明確な意味を持つと考える。

この考え方の目的は、undefinedの使用を完全に禁止することや、あらゆる「不在」を空のコレクションで表現することではない。一つのundefinedという値に、互換性のない多様な事実を持たせるのをやめることだ。空のデフォルト値は、確立された合意の「偽化可能な表現」であり、たとえ中身が空であっても、そこに型としての値が存在することを明確にする。対してundefinedは、その合意がなされる以前の、「契約上の値が全く供給されなかった、または生成されなかった」という状態を伝える。それは、空の配列でも空のオブジェクトでもなく、失敗した文字列でもなく、単に「真に未定義である」という状態を表す。

このように、Resilientが提案する規律は、分割代入のシグネチャで正直に伝えられる合意を確立し、偽化可能なデフォルト値で空の形式を通してその合意を維持する。そして、ガード句で現在のスコープが処理を継続できるかを判断する。そして、どんな値も確立されていない場合は、undefinedにまさにその状態を語らせる。これにより、偽化可能なデフォルト値が明確な合意を伝え、未知のものは未知のままにし、undefinedが真に「未定義」なものを意味するようになる。このアプローチは、コードの意図を明確にし、開発者にとっても静的解析ツールにとっても、より予測可能で理解しやすいコードベースを構築するための重要な指針となる。

関連コンテンツ

関連IT用語