Nullableの本当の意味わかってますか? 〜「とりあえず ?. で回避」が危険な理由〜
最近の言語(TypeScript, Kotlin, PHP8〜など)では、? を使った Nullable型 や、オプショナルチェーン (?.) が当たり前に使われるようになりました。
// 便利ですよね
const userName = response?.user?.name ?? 'Guest';
しかし、現場でコードを見ていると、この機能を 「エラーを出さないための安全装置」 だと誤解しているケースが非常に多いです。
「null で落ちると困るから、とりあえず ? つけておこう」
「undefined かもしれないから、?? で空文字にしておこう」
もしこう考えているなら、それは危険信号です。
Nullableは「エラー回避機能」ではありません。「データ構造の定義」です。
本記事では、この認識の違いがシステムにどのような致命的なバグを生むのか、解説します。
目次
1. Nullableの「本当の意味」とは
エンジニアが string | null (Nullable) と定義するとき、そこには明確な 「設計上の意思」 が込められていなければなりません。
✅ 正しいNullable(仕様としての欠落)
-
ミドルネーム: 日本人には無い人が多い。 $\rightarrow$
nullでOK。 -
退会日: まだ退会していない会員。 $\rightarrow$
nullでOK。
これは 「値が無い状態こそが正常」 なケースです。ここでは ?. を使ってハンドリングするのが正解です。
❌ 間違ったNullable(バグ隠蔽)
- ユーザーID: ログイン中なら絶対あるはず。
-
商品価格: 無料でも
0円であり、nullはありえない。
これらが null になるのは「仕様」ではなく 「バグ(異常事態)」 です。
ここで「落ちないように」と ?. や ?? を使うことは、異常事態を握りつぶして正常なフリをする行為です。
2. 「とりあえず」は警告灯にガムテープを貼る行為
車の運転中に「エンジン異常」の赤いランプが点灯したと想像してください。
- 正しい対応: 車を止めて、ボンネットを開けて原因を調べる(例外を吐いて止まる)。
-
「とりあえず
?.」の対応: 「ランプが眩しいな」と言って、上からガムテープを貼って見えなくする。
エラー(例外)は、システムからの 「ここで想定外のことが起きてるぞ!」という悲鳴 です。オプショナルチェーンでその悲鳴を封じ込めると、システムはどうなるでしょうか?
ゾンビプロセスの発生
// 注文IDはシステム上、絶対に必須
// しかしAPI等の不具合でデータが来ていなかったとする
const orderId = response?.data?.order?.id;
// orderId は undefined になるが、エラーは出ない(ガムテープ)
// そのまま決済処理へ進む...
executePayment(orderId);
- ここでエラーにならず、
undefinedがすり抜ける。 - 決済システムに
undefinedが送信される。 - 決済エラーになるか、最悪の場合 「IDなしの謎データ」 がDBに保存される。
バグの原因(APIの不備)と、結果(決済失敗)が離れれば離れるほど、調査は困難になります。
バグは 発生した瞬間(入り口)で殺す (Fail Fast) のが鉄則です。
3. 「デフォルト値」の罠
Null合体演算子 (??) も同様に乱用されがちです。
// 価格が取れなかったらとりあえず 0 にしとく?
const price = item.price ?? 0;
もしこれが、DB障害で価格が取得できなかったケースだとしたら?
「システムエラーのせいで、高級商品を0円(タダ)で売ってしまった」 という大事故になります。
- Null (値なし): 「まだ設定されていない」
- 0 (ゼロ): 「数値としてのゼロ」
- Empty (""): 「空の文字列」
これらは全て意味が違います。
「面倒だから ?? 0」や「?? ""」で統一してしまうのは、データの意味(ドメインロジック)を破壊する行為です。
4. 本来やるべきこと:境界線での検問
ではどうすればいいのか?
答えは 「入り口(境界線)で白黒ハッキリさせる」 ことです。
APIレスポンスを受け取った瞬間、フォームから送信された瞬間。この「境界線」でバリデーションを行います。
// 【良い設計】入り口で検問する
function fetchOrder() {
const data = api.get('/order');
// 必須項目が無ければ、ここで例外を投げて止める!
if (data.orderId == null) {
throw new Error("Critical: Order ID is missing!");
}
// ここを通過したデータは「絶対にIDがある」と保証される
return data; // 型を { orderId: string } に確定させる
}
// ロジック側
function main() {
const order = fetchOrder();
// もう ?. を使う必要はない。
// なぜなら、もし無ければ fetchOrder で既に落ちているから。
process(order.orderId);
}
JavaやPHPでよくやる「JSONをパースして専用クラス(DTO)にマッピングする」という手法は、まさにこのためにあります。
TypeScriptであれば、Zodなどのバリデーションライブラリを使って、入り口で型を確定させるのがベストプラクティスです。
まとめ
Nullableの本当の意味、わかっていましたか?
- Nullable = 「値が無い状態」が仕様として正しいこと。
-
?./??= 「仕様としての欠落」を扱うための道具。
「バグで値が無いかもしれない」という不安を消すために ?. を使うのはやめましょう。
それはバグを直しているのではなく、バグを隠蔽して、将来の自分たちを苦しめる時限爆弾を埋め込んでいるのと同じです。
「エラーを出さないこと」よりも「正しく動く(ダメなときはすぐに止まる)こと」。
この意識を持つだけで、コードの堅牢性は劇的に上がります。