プログラミングを学ぶ中で、誰もが一度は叩き落される絶望の罠、「NULL(ヌル)」。
- Javaでおなじみの全エンジニアの敵
NullPointerException(NPE) - SQLにおける独特な「三値論理」の
NULL - JavaScriptの
undefinedとnullの曖昧な境界
それぞれのレイヤーで「値が存在しない状態」の扱い方が異なるため、バックエンドとデータベースの連携部分で意図しないデータの消失やバグが連発する大事故を経験しました。
今回は、私が過去にやらかした「NULLにまつわる不具合の連発劇」と、そこから学んだ各レイヤーでのNULLとの正しい付き合い方についての考察をまとめます。
事故現場1:SQLの「三値論理」を理解しておらず、データが消失した
状況
会員テーブル(users)から、「退会フラグ(deleted_at)が立っていないアクティブなユーザー」を取得するSQLを書いた時のことです。
-- 退会日時が入っていないユーザーだけを取得したい
SELECT * FROM users
WHERE deleted_at <> '2026-01-01 00:00:00';
「よし、退会していないユーザー(deleted_at が NULL のユーザー)が取れてるな!」と思い込み、そのまま本番へ投入。
発生した悲劇
いざ検索クエリを実行すると、**アクティブユーザーが1件も取得できない(結果が0件になる)**という事態が発生しました。
SQLの世界は TRUE / FALSE の二値ではなく、**TRUE / FALSE / UNKNOWN の「三値論理」**で動いています。
NULL は「値が存在しない(不明)」であるため、<>(等しくない)や =(等しい)などの比較演算子を使った瞬間、評価結果がすべて UNKNOWN になり、WHERE 句の条件から除外されてしまっていたのです。
考察と反省
-
SQLの
NULLは値ではない: 「空文字」や「0」とは違い、「判定不能」であることを忘れていた。 -
IS NULL/IS NOT NULLの徹底: 比較演算子ではなく、専用の述語を使わなければならない。
事故現場2:Javaの String.valueOf(null) が生み出した「文字列 "null"」の罠
状況
Javaのバックエンド処理で、DBから取得した値(NULL の可能性があるフィールド)を文字列に変換して外部APIへ送信、またはフロントへ渡す処理を実装しました。
「NPE(NullPointerException)を起こしたくないから、安全に文字列変換しよう!」と考え、String.valueOf() を使いました。
// DBから取得した値(nullが入っている)
String categoryCode = dbResult.getCategoryCode();
// NPEを防ぐために String.valueOf を使用
String safeCategory = String.valueOf(categoryCode);
if (safeCategory != null) {
// nullじゃないから処理を実行!
processCategory(safeCategory);
}
発生した悲劇
NPEは出ず、一見正常に動いているように見えました。
しかし、データベース側で categoryCode が NULL だったデータに対して、なぜか**「存在しないカテゴリ "null" が指定されました」**という謎のエラーが多発。
実は String.valueOf(null) は、null チェックを行って空文字や null を返すのではなく、文字列の "null"(4文字のString)を返してしまうという罠仕様があったのです。
結果として、safeCategory には文字列の "null" が入り、if (safeCategory != null) のチェックを悠々とスルーして後続の処理を破壊していました。
なぜこの悲劇は繰り返されるのか?(全体の考察)
システム全体(フロント〜バックエンド〜DB)を見渡したとき、「値が存在しない」という状態の表現方法が分散していることが混乱の根本原因です。
各レイヤーにおける「値がない状態」の解像度
| レイヤー | 表現 | 特徴・罠 |
|---|---|---|
| SQL (DB) | NULL |
三値論理。= や <> で比較すると壊れる。IS NULL が必須。 |
| Java (Backend) | null |
参照がない状態。触った瞬間に NullPointerException が発生。 |
| JavaScript (Frontend) |
null / undefined
|
定義されていない (undefined) と 空である (null) が混在。 |
開発者が「たぶん空だろう」という感覚値で各レイヤーを跨いでデータを流すと、どこかで意味論(セマンティクス)の不一致が起きてバグが跳ね返ってきます。
失敗から得た「NULLと戦うためのアクションプラン」
同じ失敗を繰り返さないために、私は以下の設計指針を徹底するようにしました。
1. DB設計レベルで可能な限り NOT NULL 制約をつける
根本的な解決策として、**「そもそもテーブルに NULL を入れない」**というアプローチです。
- デフォルト値(
DEFAULT ''やDEFAULT 0)を設定できるカラムは、原則NOT NULLにする。 - 「NULL が入る余地」を減らすことで、SQLの三値論理の罠を回避する。
2. Java側では Optional や Objects.requireNonNullElse を使う
null を生のまま持ち回すのをやめ、Java 8以降の標準機能を活用します。
// String.valueOf(null) は危険。"null" という文字列になる
// 代わりに Objects.toString を使うとデフォルト値を指定できる
String category = Objects.toString(dbResult.getCategoryCode(), "");
// Optionalを使って明示的に値の有無を扱う
Optional.ofNullable(dbResult.getCategoryCode())
.ifPresent(code -> processCategory(code));
3. フロントに渡す前にレスポンス(DTO)で揺らぎを吸収する
DBから取れた NULL をそのままJSON化してフロントに投げると、JS側で null になったり "null" になったりして混ざります。
バックエンドの境界線(Controller / DTO)で、「空文字 "" に統一する」あるいは「キー自体を含めない」といったルールを明確に統一するようにしました。
おわりに
「NULL」は、一見単純に見えてプログラミング言語やDBの根本思想が色濃く出るテーマです。
- SQLは
IS NULLで判定する - Javaで安易に
String.valueOf()を信じない - レイヤーの境界線で「空」の定義を揃える
この基本を守るだけで、システム全体の堅牢性は劇的に上がると痛感しました。
皆さんも「NULLに弄ばれて夜を明かした」思い出があれば、ぜひコメントで教えてください!
株式会社ONE WEDGE
【ITエンジニアに、IT業界に貢献する企業】
株式会社ONE WEDGEは、Webシステム開発・SES・AI/DX支援を行うIT企業です。生成AIを活用した業務効率化や次世代システム開発にも注力しており、企業の課題解決だけでなく、エンジニア一人ひとりの成長にも本気で向き合っています。また、技術は「一人で学ぶもの」ではなく「仲間と成長するもの」と考え、社内外でのコミュニティづくりにも力を入れています。
https://onewedge.co.jp/