技術ノート | AI駆動開発の、品質の話
リリース前、画面はすべてグリーンだった。型チェックも、ユニットテストも、ビルドも、全部パス。普通なら「よし、出そう」となる。
だが、自社プロダクト(AI検索チェック)のリリース前総点検で、全テストを通過したまま潜んでいた重大バグを3件見つけた。どれも、コードを眺めても気づきにくい。共通点はひとつ——何のシグナルも出さずに、静かに壊れる。エラーも警告も出ないから、「動いているように見える」。
関連記事 | この点検の対象プロダクトあなたの店は、AIにどう紹介されている? ──「AI検索チェック」を専門用語ゼロで作った話
→
この「動いて見える」が、いちばん危ない。派手にクラッシュするバグは気づけるが、静かな失敗は本番で長く生き残る。今回はそれを、実データで実APIを叩く・実メールを送る・DBの実値を読み戻す——つまり「実機検証ファースト」で捕まえた。その3類型と、捕まえ方を書く。

01罠①:タイムゾーンの二重変換 ──「本番で動く/ローカルで壊れる」
最初の罠は、日付の計算にあった。「毎日0時(日本時間)でリセットする」という、よくある仕様だ。
問題のコードは、こういう発想で書かれていた。「ローカル日付を取って、そこから9時間引けばUTCになるだろう」。一見もっともらしい。だが、これは「サーバーはUTCで動いている」という暗黙の前提が混入した二重変換だった。サーバーがUTCなら偶然正しく動く。ところがローカル(日本時間のマシン)で動かすと、リセット時刻が"過去"になり、毎回「もうリセット済み」と判定される——つまり、1日1回のはずの制限が、何回でも通ってしまう。
怖いのは、本番(UTC)では正しく動いてしまうことだ。だから本番テストでは発覚しない。「本番では動くのに、手元では壊れる」という非対称が出たら、それはタイムゾーン前提が紛れ込んでいるシグナルだ。対策はシンプルで、Date.UTC ベースのタイムゾーンに依存しない計算に書き換えること。そして検証では、「2回連続で叩いて 200 → 429(制限)になるか」を実際に確かめ、保存されたリセット時刻の実値を読み戻す。"そういう仕様のはず"ではなく、"本当にそうなっているか"を機械で見る。
なぜユニットテストで漏れたのか。テストは普通、**1つの環境(多くはUTC前提のCI)でしか走らない**からだ。テストの中では時計を固定したり、特定のタイムゾーンを暗黙に仮定したりする。だから「環境が変わると結果が変わる」種類のバグは、**1環境のテストでは原理的に見えない**。`toLocaleDateString` のような"環境に依存する関数"を日付ロジックの真ん中に置いた瞬間、コードは静かに環境依存になる——テストは緑のまま。
02罠②:モックフォールバック ──失敗を"嘘の成功"に化かす
2つ目は、外部API(検索データやLLM)が失敗したときの処理に潜んでいた。
開発中は、APIキーが無くても動くように「モック(ダミーデータ)」を返す仕組みを入れることがある。これ自体は便利だ。問題は、その実装が「実行時にAPIがタイムアウト・エラーした時」にも同じモックへ落ちていたこと。すると——本番で外部APIがこけても、架空のもっともらしいデータが代わりに流れ込み、診断結果として表示される。エラーは握り潰され、画面上は「成功」に見える。

診断・分析系のプロダクトでは、これは最悪の事故だ。出力の"真実性"こそがプロダクトの価値だからだ。嘘のデータで「あなたの店は良好です」と出してしまっては、存在意義が崩れる。原則は「設定が無い」と「実行が失敗した」を絶対に混ぜないこと。モックを返すのはキー未設定(明示的な開発モード)のときだけ。実行時の失敗は黙って隠さず、そのコールはスキップして「データ欠落」として正直に縮退させる(捏造より欠落)。あわせて、タイムアウトは実APIの応答実態に合わせて延ばす(短すぎると正常でも失敗扱いになる)。
この罠が生まれるのは、たいてい"親切心"からだ。「落ちても画面が壊れないように、何か返しておこう」。だが診断・分析系では、その親切が逆効果になる。**ユーザーは"結果が出た=正しい結果"と受け取る**からだ。だから私たちは、外部依存のある処理には「失敗したら、もっともらしく取り繕わず、欠けていると正直に見せる」を原則にしている。一部のデータが欠けた診断は、全部が嘘の診断より、はるかに誠実で安全だ。捏造より欠落——これは透明性を掲げるCAGにとって、技術というより姿勢の問題でもある。
03罠③:fire-and-forget ──送ったつもりで、握り潰す
3つ目は、メール送信にあった。
通知メールを送る処理が、結果を待たずに投げっぱなし(fire-and-forget)になっていた。送信関数を呼ぶだけ呼んで、その戻り値(成功したか・失敗したか)を確認しない。すると、送信に失敗していても、誰も気づかない。ユーザーは「届くはず」と思い、こちらは「送ったはず」と思い、実際には何も届いていない——という、いちばん厄介なすれ違いが起きる。
対策は地味だが効く。送信結果を await して、ちゃんと確認する。失敗したらログに残し、必要なら再送やアラートにつなぐ。「投げて終わり」を「投げて、着いたか確かめる」に変えるだけで、握り潰しは消える。外部に何かを送る処理は、ほぼ必ず失敗しうる——その前提で、結果を見届けるところまでを一つの処理にする。
とくにサーバーレス環境では、これは事故に直結する。レスポンスを返した瞬間に関数が終了し、待たれていない送信処理は途中で打ち切られることがあるからだ。「ローカルでは届くのに本番だけ届かない」——これもまた、環境差による"静かな失敗"の典型だ。`await` して結果を見届ける、という地味な一手が、ここでも効く。
043つの共通点は、「静かに壊れる」こと
並べてみると、3つの罠は症状が全く違う。日付計算、API失敗時の処理、メール送信。だが、根っこは同じだ。
どれも、何のシグナルも出さずに壊れる。例外も赤いエラーも出ない。テストもビルドも通る。だから「動いているように見える」。
そして、自動化や外部連携を信頼するほど、このシグナルの出ない失敗の破壊力は大きくなる。気づいたときには、間違ったデータが配られ、届くべき通知が消え、制限が効いていない——後から効いてくる。
これは、私たちが繰り返し書いてきたテーマと同じ家系だ。AIが「✅ できました」と報告しながら実は何もしていない、あの「サイレントな成功」。人が書いてもAIが書いても、いちばん怖いのは派手な失敗ではなく、静かな失敗だ。
関連記事 | 同じ"サイレント失敗"の家系Claudeが「court」とだけ呟いて、静かに止まる日 ──"壊れたお手本"を自己増殖させるバグと、職人の対処法
→
05捕まえ方:実機検証ファースト
では、テストを通り抜けるこの手の失敗を、どう捕まえるか。答えは一つしかない。実機で、本物を通すことだ。
- 実在データで実APIを叩く:架空のテストデータではなく、本物の入力で本物の外部APIを呼ぶ。モックフォールバックが嘘をついていれば、ここで露見する。
- 実メールを送って、着信を確かめる:fire-and-forgetの握り潰しは、実際に送って受信箱を見るまで分からない。
- DBの実値を読み戻す:「保存したはず」を信じず、書かれた値を読み戻して期待どおりか照合する(リセット時刻の罠はこれで捕まえた)。
- 境界条件を実際に踏む:「2回叩いて 200 → 429」のように、仕様の境目を本当に通してみる。
型チェックもユニットテストも、もちろん価値がある。だがそれらは「コードが想定どおり書けているか」までしか保証しない。「本番の環境・本物のデータ・外部サービスの気まぐれの中で、本当に正しく動くか」は、実機で通して初めて分かる。CAGがリリース前に必ず実機検証を挟むのは、このためだ。
そして、こうした検証は"気をつける"では続かない。だからCAGでは、見つけた罠を**リリース前チェックの定番項目として残す**。「レート制限は2連続で200→429になるか」「外部API停止時にモックが混ざらないか」「通知メールは実際に着信するか」——一度痛い目を見た観点を、次回から機械的に通せる形にしておく。同じ静かな失敗を二度踏まない仕組みにして初めて、知見は資産になる。
06締め:テスト通過は、ゴールではなく出発点
3つの罠は、どれも「コードは正しく書けている」のに本番で壊れる類のものだった。だからこそ、全テスト通過=安心、ではない。それは「出発点に立った」だけだ。
AI駆動で開発スピードが上がるほど、"動いて見える"ものが速く大量に出てくる。そのスピードを品質に変えるのは、最後に実機で本物を通して確かめる規律だ。派手に転ぶバグは誰でも気づく。プロに問われるのは、シグナルを出さずに静かに壊れる失敗を、出荷前に捕まえられるかだ。
面白いのは、今回の3件はどれも「高度な不具合」ではないことだ。日付計算、失敗時の分岐、送信結果の確認——基礎的な部分にこそ、静かな失敗は宿る。だからこそ「動いて見えるから大丈夫」を疑い、基礎を実機で一つずつ通す。地味だが、ここで手を抜かないことが、信頼されるプロダクトと「なんか時々おかしい」プロダクトを分ける。

CAGは、速く作る。そして、速く作るからこそ、"静かに壊れていないか"を実機で確かめてから出す。それが、AI駆動開発を「速いけど雑」にしないための、譲れない一線だ。速さと品質は、トレードオフではない——確かめる規律さえあれば、両立できる。
テスト通過で安心 vs 実機検証ファースト。同じ「テスト緑」でも、品質は別物だ。
| 観点 | テスト通過で安心 | CAG(実機検証ファースト) |
|---|---|---|
| 何を保証するか | コードが想定どおり書けている | 本番・本物のデータで正しく動く |
| TZ/環境差 | 本番で動けばOK | 本番/手元の非対称を疑い実値で照合 |
| 外部API失敗 | モックで握り潰し(嘘の成功) | 失敗はスキップ=欠落で正直に縮退 |
| メール等の送信 | 投げて終わり | 結果を await して着信確認 |
| 静かな失敗 | 本番で長く生き残る | 出荷前に実機で捕まえる |
| スピードとの関係 | 速い=雑になりがち | 速く作り、実機で確かめて出す |
※ 本記事はCAGの自社プロダクト(AI検索チェック)のリリース前総点検で実際に発見・修正した3件のバグを、技術的な教訓として一般化したもの。外部API名・内部の具体的な実装値・シークレットは含まない(v0・2026-06時点)。
脚注・出典
- 本記事はCAGの自社プロダクト(AI検索チェック)のリリース前総点検で実際に発見・修正した3件のバグを、技術的な教訓として一般化したもの。外部API名・内部の具体的な実装値・シークレットは含まない。
- 「設定が無い」と「実行が失敗した」を混ぜない、というモックフォールバックの原則は、診断・分析系プロダクト(出力の真実性が価値の中核)で特に重要。
- 「シグナルを出さずに静かに壊れる失敗(サイレント失敗)」は、CAGが別記事でも繰り返し扱っているテーマ。人が書いたコードでもAIが書いたコードでも共通する。
「動いて見える」を、出荷前に疑う。
CAGは速く作るだけでなく、実データ・実API・実環境で"本当に動くか"を確かめてから出します。テストが通っても残る静かなバグを、出荷前に捕まえる。品質を落とさないAI駆動開発を、一緒に。まずはこのサイトのAIに相談してみてください。
このAIに相談する →








