最初から安定していたわけではありません。画面が真っ白になり、「解析中」から進まず、グラフだけが消え、WordPressの仕様にもぶつかる。 それでも一つずつ原因を分解し、最終的に「壊れにくい設計」へたどり着きました。
─ バグは設計を強くする ─
最初に、正直に言います。このAI診断ツールは、最初から安定していたわけではありません。 むしろ開発初期は、かなり手強い状態でした。
- 画面が真っ白 何も表示されず、沈黙する。
- 「解析中」で停止 処理が戻らず、無限ループのように見える。
- グラフだけ出ない 本文は出るのに、可視化だけ欠落する。
- scriptが消える WordPress側の仕様や最適化にぶつかる。
まさに、完全なエラー地獄です。 しかし、その失敗があったからこそ、今の「壊れにくい設計」にたどり着きました。
この記事では、AI診断ツールの裏側で実際に起きた問題と、それをどう設計で乗り越えたのかを、開発記録として公開します。
1. 「ID地獄」──命令が混線する瞬間
初期設計では、document.getElementById("zai-next") のような、ごく一般的な書き方に頼っていました。
1ページに診断ツールが1つだけなら、それでも大きな問題はありません。
ところが、同じ診断ツールを同じページに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]');
この変更によって、ツールはページ全体に依存せず、自分の領域の中で完結して動くようになりました。 いわば、独立独歩の設計です。
2. 「解析中」から進まないゴースト現象
次に悩まされたのが、「解析中」のまま進まない現象です。 これが一番のホラーでした。
リセットしたはずなのに、1.4秒後に前の診断結果が突然表示される。 もう消したはずの処理が、時間差で戻ってくるのです。
原因は、裏側で動き続けていたタイマー処理の競合でした。 新しい診断を開始しても、古いタイマーがまだ生きていると、あとから結果表示を上書きしてしまいます。
解決:runIdによる「世代管理」
そこで導入したのが、runId による世代管理です。
新しい診断が始まるたびに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設計 です。

