AIとWeb3を“実験”しながら理解するための、技術検証ラボ

    AI診断ツール自作で直面した「エラー地獄」と、たどり着いた「鉄壁(IRONCLAD)」の設計思想

    サイバーパンクなデジタルグリッド背景に、中央に輝く六角形のデジタルシールドアイコン。左側に赤いエラー光片と「エラー地獄から」、右側に水色の「鉄壁の設計へDESIGN」の日本語テキスト。下部に「IRONCLAD」の文字と、ガーディアンロボット(左)とバグロボット(右)のアイコン、そして「バグは設計を強くする。」のテキスト。左上に「ZEUS RESEARCH LAB / DEVELOPMENT NOTES」のロゴ。
    Zeus Research Lab / Development Notes
    AI診断ツールを自作して直面した「エラー地獄」と、たどり着いた「鉄壁」の設計

    最初から安定していたわけではありません。画面が真っ白になり、「解析中」から進まず、グラフだけが消え、WordPressの仕様にもぶつかる。 それでも一つずつ原因を分解し、最終的に「壊れにくい設計」へたどり着きました。

    2026年5月16日 AI活用 開発秘話 IRONCLAD Design

    ─ バグは設計を強くする ─

    最初に、正直に言います。このAI診断ツールは、最初から安定していたわけではありません。 むしろ開発初期は、かなり手強い状態でした。

    • 画面が真っ白 何も表示されず、沈黙する。
    • 「解析中」で停止 処理が戻らず、無限ループのように見える。
    • グラフだけ出ない 本文は出るのに、可視化だけ欠落する。
    • scriptが消える WordPress側の仕様や最適化にぶつかる。

    まさに、完全なエラー地獄です。 しかし、その失敗があったからこそ、今の「壊れにくい設計」にたどり着きました。

    この記事では、AI診断ツールの裏側で実際に起きた問題と、それをどう設計で乗り越えたのかを、開発記録として公開します。

    1. 「ID地獄」──命令が混線する瞬間

    初期設計では、document.getElementById("zai-next") のような、ごく一般的な書き方に頼っていました。 1ページに診断ツールが1つだけなら、それでも大きな問題はありません。

    ところが、同じ診断ツールを同じページに2つ置いた瞬間、ツールは「自分自身」を見失いました。

    たとえるなら、同じ名前の人が2人いる部屋で「山田さん!」と呼ぶようなものです。
    ブラウザは、どちらのボタンに命令を届ければよいのか判断しづらくなり、結果として処理の混線が起きます。

    解決:スコープ設計、つまりカプセル化

    対策として、ページ全体から要素を探すのをやめました。 代わりに、「自分の親要素、つまりrootの中だけを探す」設計に変更しました。

    ※以下は考え方を伝えるために簡略化したコード例です。前作の診断ツールと整合しやすいよう、接頭辞は zai- 系に統一しています。

    const root = document.querySelector('[data-zai-root]');
    const find = (selector) => root.querySelector(selector);
    
    const nextButton = find('[data-zai-next]');
    const resultArea = find('[data-zai-result]');
    複数設置に強い 同じページ内に複数の診断ツールを置いても、それぞれが自分の範囲だけで動作します。
    プラグイン干渉を抑える WordPress内の他のパーツやプラグインと命令が衝突しにくくなります。
    保守しやすい 処理の責任範囲が明確になり、不具合が起きたときの原因追跡がしやすくなります。

    この変更によって、ツールはページ全体に依存せず、自分の領域の中で完結して動くようになりました。 いわば、独立独歩の設計です。

    2. 「解析中」から進まないゴースト現象

    次に悩まされたのが、「解析中」のまま進まない現象です。 これが一番のホラーでした。

    リセットしたはずなのに、1.4秒後に前の診断結果が突然表示される。 もう消したはずの処理が、時間差で戻ってくるのです。

    原因は、裏側で動き続けていたタイマー処理の競合でした。 新しい診断を開始しても、古いタイマーがまだ生きていると、あとから結果表示を上書きしてしまいます。

    解決:runIdによる「世代管理」

    そこで導入したのが、runId による世代管理です。 新しい診断が始まるたびにIDを更新し、古い診断処理には潔く引退してもらう仕組みにしました。

    新しい診断開始 現在のrunIdを更新する。
    タイマー処理が完了 結果表示前にIDを確認する。
    IDが一致した時だけ表示 古い処理は何もせず終了する。

    ※タイマーや非同期処理が複数走る場面では、現在の処理だけを有効にするための「世代管理」が重要になります。

    const taskRunId = currentRunId;
    
    setTimeout(() => {
      if (taskRunId !== currentRunId) {
        return;
      }
    
      showResult();
    }, 1400);
    結果表示のゲートキーパー:
    currentRunId === taskRunId のときだけ、画面に結果を出す。 この条件によって、古いタイマーが後から割り込むことを防ぎます。

    新しい診断が始まるとIDが更新されるため、古いタイマーは「自分はもう古い世代だ」と判断し、自ら処理を中止します。 これで、幽霊のように戻ってくるゴースト現象は消えました。

    3. SWELL高速化との戦い

    WordPressテーマ「SWELL」の高速化機能は非常に優秀です。 ただ、その強力さゆえに、開発中のツール側では別の問題が発生しました。

    具体的には、グラフ描画用のライブラリが読み込まれる前に、ツール側の描画命令が先に走ってしまうケースです。 速すぎるがゆえの事故でした。

    解決:ロード完了まで待つ「紳士的な設計」

    対策はシンプルです。 必要なライブラリが読み込まれるまで、ツール側が勝手に走らないようにしました。

    await ChartLoader.load();
    
    renderChart();

    この一文によって、グラフ描画の準備が整うまで、ツールは静かに待機します。 つまり、WordPress側の高速化と、ツール側の安定性を衝突させない設計です。

    急がない ライブラリが未準備の状態で描画命令を出さない。
    壊れない 読み込み順のズレがあっても、結果表示が破綻しにくい。
    共存する テーマの高速化設定とツールの安定性を両立しやすくする。

    4. セキュリティの「見えない意地」

    ここは少しだけ、開発者としてこだわった部分です。 特に意識したのが、画面へ文字を表示するときの処理方法でした。

    便利だからといって、安易に innerHTML に頼ると、意図しないHTMLやスクリプトを解釈してしまう可能性があります。 そこで、ユーザー入力や診断結果の表示には、原則として textContent を使う設計にしました。

    処理方法 入力内容の例 表示結果 評価
    innerHTML <script>...</script> HTMLとして解釈される可能性がある 注意が必要
    textContent <script>...</script> ただの文字列として表示される 安全寄り

    正直に言えば、innerHTML のほうが開発は楽です。 しかし、初心者向けの診断ツールだからこそ、裏側はできるだけ堅くしたい。

    見栄えより、安全。
    派手な表示よりも、まずは意図しないコードを実行しないこと。 ここは、ツール全体の信頼性に直結する部分だと考えています。

    5. 「失敗」を「仕様」に変えた瞬間

    ある時、気づきました。 エラーを完全にゼロにすることはできない。 それなら、失敗しても壊れないように設計すればいいのだと。

    • 情報の保証 グラフが出なくても、結果は文字で表示する。
    • 非同期の寛容 読み込みが遅れても、準備が整うまで待つ。
    • 世代の交代 古い処理は、潔く捨てる。

    これが、最終的にたどり着いた 鉄壁、IRONCLAD設計 です。

    最後に

    開発中、コンソール画面は何度も赤文字のエラーで埋め尽くされました。 しかし、その一つ一つのエラーが、ツールの骨組みを強くしてくれました。

    このツールの裏側には、書ききれないほどの試行錯誤があります。 だからこそ、ただ動くものではなく、失敗しても破綻しにくいものを目指しました。

    バグは設計を強くする。

    もしあなたが何かを作っていて、今うまくいっていなくても大丈夫です。 その失敗はきっと、あなたの設計を唯一無二の強固なものに変えてくれます。

    目次