欠損データバグの9割は要件定義で防げる
機能要件は書けても、データ要件は書き忘れる
要件定義書には「何ができるか」は詳しく書かれる。画面遷移、ボタンの挙動、APIのレスポンス形。しかし「そのデータ自体」に関する要件は抜け落ちやすい。新しいフィールドを追加するとき、初期値は何か。既存データにそのフィールドが無い場合どう扱うか。いつまで保持するか。これらは仕様書のどこにも書かれず、実装者がその場で「たぶんこれでいいだろう」と判断してしまう。
その場しのぎの判断は大抵動く。動くから誰も気づかない。問題が表面化するのは、判断されなかったケース――多くは既存データや移行データ――に実際にアクセスが発生したときだ。
実例: 「配送予定日」が無い注文だけ画面が真っ白になった
あるECサイトで「注文一覧に配送予定日を表示する」という機能要件が実装された。新規注文には配送予定日を計算して保存するロジックが入り、表示側も問題なく動いた。QAも新規注文のパターンでは全て通過した。
ところがリリース後、一部の古い注文の詳細画面を開くと真っ白になるという報告が来た。原因は単純だった。配送予定日フィールドはリリース日以降に作られた注文にしか入っておらず、それ以前の注文では NULL だった。表示コンポーネントは「配送予定日は必ず値がある」という前提で日付フォーマット処理を書いており、NULL を渡されて例外を投げていた。
機能要件のレビューでは「配送予定日をどう表示するか」は議論されたが、「配送予定日が無いデータをどう表示するか」は誰も問わなかった。要件定義の段階で移行データの扱いを一行決めておくだけで防げたバグだった。
データ要件チェックリスト
新しいフィールド・テーブル・コレクションを設計するたびに、以下の4項目を要件定義の時点で埋める。「後で決める」を許さないのがポイントだ。
1. 初期値
新規作成時のデフォルト値は何か。空文字・0・false・現在時刻など、業務的に意味のある値を明示する。「とりあえず null」は初期値の決定を先送りしているだけで、決定ではない。
2. 必須 / 任意(NULL許容)
そのフィールドは常に値を持つと仮定してよいか。フロントエンド・API・DB の3層でこの前提が一致しているかを確認する。DB は NULL 許容なのに、フロント側のコードは「絶対に値がある」前提で書かれている、というズレが典型的な事故の原因になる。
3. 移行データの扱い
既存レコードにこのフィールドをどう埋めるか。パターンは主に3つ。
- バックフィルする(既存レコード全件に一括で値を入れる)
- NULL のまま残し、読み取り側で欠損を前提としたフォールバック処理を書く
- 対象外として扱う(旧データはこの機能の対象にしない、と明示的に決める)
どれを選んでもよいが、選ばずに実装に入るのが一番まずい。
4. 保持期間
このデータをいつまで保持するか。削除・匿名化のタイミングと、その根拠(法務要件・コンプライアンス・単なるストレージコスト)を決めておく。個人情報を含むフィールドほど、後から「いつ消すか」を決めようとすると社内調整に時間がかかる。
要件定義のレビューに組み込む
この4項目は、機能要件のレビューとは別に、変更するデータ項目ごとに小さな表を作ると運用しやすい。
| フィールド | 初期値 | NULL許容 | 移行データ | 保持期間 |
|-------------------------|-------------|----------|--------------------|--------------|
| shipping_estimated_at | null不可 | 不可 | バックフィル済み | 注文と同じ |
| referral_source | "unknown" | 可 | 対象外(新規のみ) | 2年 |
「未定」というセルを一つでも残したままレビューを通さない。これだけで、後工程に判断を持ち越すことがなくなる。
Bugoon での実践
チェックリストを運用しても、見落としはゼロにはならない。欠損データバグは「特定の古いデータでだけ画面が壊れる」という再現しにくい形で表面化しやすく、報告する側もどのレコードが原因か分からないまま「この画面が真っ白になる」としか伝えられないことが多い。
Bugoon のウィジェットならスクリーンショットと操作ステップを添えてその場で報告でき、GitHub Issue に連携すれば開発側はどの画面のどの状態で発生したかを一次情報付きで受け取れる。原因の当たりをつける時間が短くなる分、要件定義に立ち返って「この移行データの扱いを決めていなかった」という根本原因にもたどり着きやすくなる。