エージェントでコーディングする時代でも必要な基礎知識
AI生成コードを自信を持ってレビューするための基礎を解説。HTTP、ブラウザー、状態管理、セキュリティ、デバッグ、Git、設計を学べます。
プログラミングの基礎知識は、コードを自分で書くためではなく、エージェントが書いたコードを評価するために必要です。動くコードであっても、設計が悪かったり、セキュリティに問題があったり、遅かったりすることがあるからです。
よくあるのは次のような状況です。エージェントが「完了しました」と報告し、ページは表示され、テストも通る。それでも、レビュアーに差分について聞かれたら説明できない。こうした不安はもっともなもので、多くの場合、自分の知識のどこかに具体的な穴があることを示しています。
本記事では、プロンプトの書き方やツールの設定は扱いません。それらについては、AIコーディングツールで最良の結果を得る方法やAIコーディングのヒントとコツを参照してください。本記事では、Webの基礎知識を7つ取り上げ、それぞれについてエージェントが実際に犯しがちなミスと、それを見抜くために必要な知識を組み合わせて解説します。
要点
- ブラウザの
fetch()は、ネットワークエラーなどリクエスト自体が失敗した場合にのみ reject されます。そのため、response.okをチェックしないコードは 404 や 500 を成功として扱ってしまいます。 - フロントエンドのJavaScriptにバンドルされた値は、フレームワークがクライアントに公開する環境変数も含め、すべて公開情報です。
- ボタンを非表示にすることは認可ではありません。サーバーがすべてのリクエストで権限をチェックする必要があります。
git add -pを使えばエージェントの変更をハンク単位で取り込むことができ、git revertを使えば履歴を書き換えずにコミットを取り消せます。
| 基礎知識 | エージェントが犯しがちなミス | 見抜くための知識 |
|---|---|---|
| HTTP | エラー内容を含むボディを 200 で返す | ステータスコードのセマンティクス |
| ブラウザ | クリーンアップのないリスナー | Effect のライフサイクル |
| 状態管理 | サーバーデータをストアにコピーする | 信頼できる唯一の情報源(Single Source of Truth) |
| セキュリティ | クライアントバンドル内のシークレット | ブラウザに何が配信されるかの理解 |
| デバッグ | エラーの握りつぶし | スタックトレース、DevTools |
| Git | すべての変更を一括で受け入れる | ハンク単位のステージング、revert |
| アーキテクチャ | 1行で済む処理に新しい依存関係を追加 | プラットフォームAPI |
なぜ今でもHTTPステータスコードとキャッシュが重要なのか
ステータスコードとキャッシュヘッダーは、クライアント、プロキシ、監視ツールがリクエストの結果を把握するための手段です。ボディにエラーメッセージを入れて 200 OK を返すエンドポイントは、すべてのクライアント、キャッシュ、監視ツールに対して「リクエストは成功した」と伝えていることになります。RFC 9110 では 2xx を成功と定義しているため、エラートラッキングもリトライも作動しません。ハンドラーをレビューする際は、HTTPステータスコードのリファレンスを手元に置いておきましょう。
クライアント側にも対応する落とし穴があります。fetch() はHTTPのエラーステータスでは reject されず、リクエスト自体が失敗した場合にのみ reject されます。そのため、Response.ok を自分でチェックする必要があります。
const res = await fetch('/api/save', { method: 'POST', body });
if (!res.ok) throw new Error(`Save failed: ${res.status}`);
また、エージェントはめったに変わらないデータをポーリングしがちです。設定情報や参照用のリストであれば、Cache-Control(RFC 9111 で定義)と ETag による再検証(RFC 9110 で定義された 304 Not Modified を返す)を組み合わせることで、冗長なリクエストをなくせます。本当にリアルタイムで変化するデータは、また別の問題です。
ブラウザはどのようにレンダリングし、イベントを処理するのか
イベントリスナーやDOMへの書き込みにはライフサイクルがあり、それを無視すればブラウザ側で代償を払うことになります。エージェントによくあるミスは、クリーンアップも依存配列もない Effect の中でリスナーを追加することです。React はそのような Effect をレンダリングのたびに実行するため、レンダリングのたびに resize リスナーが1つずつ追加されていきます。修正版では、クリーンアップ関数を返し、依存関係を宣言します。
useEffect(() => {
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, [onResize]);
返されたクリーンアップ関数は、次に Effect が実行される前と、アンマウント時に実行されます。ブラウザに関してもう1つ見抜くべきパターンが、レイアウトスラッシングです。これは、スタイルを書き込んでから offsetHeight を読み取る処理をループで何度も繰り返すものです。書き込み後の読み取りは、そのたびに強制的な同期レイアウトを引き起こす可能性があります。修正方法は、先にすべての読み取りを行い、その後で書き込みを行うことです。
状態はどこに置き、データはどのように流すべきか
すべてのデータには、所有者が1つだけあるべきです。サーバーデータをクライアント側のストアにコピーすると、2つ目の情報源が生まれ、一方だけが変更された時点で2つのコピーは食い違い始めます。エージェントが書く典型的なコードでは、リストを取得して useState や Zustand のストアに入れ、ローカルで編集する一方で、再取得では別のコピーが更新されます。
React の状態構造に関するガイドでは、状態の重複を避けるよう警告しています。サーバーデータについては、TanStack Query や SWR のようなキャッシュレイヤーにデータを所有させ、無効化も任せましょう。既存の状態から計算できるものは、レンダリング中に値を導出します。
セキュリティの基本:認証、XSS、シークレット、信頼できない入力
フロントエンドのセキュリティは、どのコードがブラウザで実行され、どのコードがサーバーで実行されるかを理解しているかどうかにかかっています。フロントエンドのJavaScriptにバンドルされた値は、すべて公開情報です。Next.js では、環境変数はデフォルトでサーバー専用です。NEXT_PUBLIC_ プレフィックスを付けると、ビルド時に変数の値がクライアントバンドルにインライン展開されます。つまり、APIキーにこのプレフィックスを付けたエージェントは、そのキーを公開してしまったことになります。
UI上でボタンを非表示にすることは認可ではありません。誰でもAPIを直接呼び出せるため、サーバーがすべてのリクエストで権限をチェックする必要があります。この種の欠陥は、OWASP Top 10:2025 の A01 Broken Access Control(アクセス制御の不備)に該当します。
ユーザー入力を innerHTML に代入すると、ブラウザはそれをマークアップとして解析します。そこには、JavaScriptを実行する onerror のような属性も含まれます。textContent を使えば、同じ入力がプレーンテキストとして挿入されます。JSX は埋め込まれた文字列をデフォルトでエスケープしますが、dangerouslySetInnerHTML はその保護を無効にします。それ以外の点については、OWASP XSS Prevention Cheat Sheet を参照してください。
自分で書いていないコードをどうデバッグするか
デバッグとは、バグの原因を突き止めることであり、症状を消すことではありません。エージェントは、エラーを握りつぶす try/catch で囲んだり、undefined の値がどこから来たのかを追跡せずにオプショナルチェーンで隠したりして、エラーを「修正」しがちです。コンソールからエラーは消えますが、バグは残ったままです。
修正を受け入れる前に、スタックトレースを読み、バグを再現し、Chrome DevTools の Network パネルでリクエストとレスポンスに実際に何が含まれていたかを確認しましょう。実際のユーザーの環境でしか発生しないバグもあります。セッションリプレイを使えば、エンドポイントを繰り返し叩き続けるリトライループや、何も表示せずに送信に失敗するフォームなど、エージェントが書いたコードが本番環境のユーザーに対して実際にどう動作しているかを確認できます。こうした基礎知識の穴が見えてくるのは、多くの場合そこです。
エージェントの変更をレビューし、取り消せる程度のGit知識
Git は、エージェントの作業の一部を受け入れ、残りを取り消すための手段です。エージェントは広範囲にわたる編集を行いがちで、それを一度に受け入れると無関係な変更が紛れ込んでも気づけません。必要な操作のほとんどは、次の4つのコマンドでカバーできます。
git diff # read every change
git add -p # stage hunk by hunk
git restore -p src/x.ts # discard unwanted hunks
git revert <commit> # undo a committed change
git add -p はステージングの前にハンクごとに確認を求め、git restore -p はワーキングツリーのハンクを1つずつ破棄します。git revert は、以前のコミットを打ち消す新しいコミットを追加します。そのため共有ブランチでも安全に使えます。一方、reset は履歴を書き換えます。
過剰な設計を見抜ける程度のアーキテクチャ知識
優れたアーキテクチャとは、プロジェクトにすでにあるもので目の前の問題を解決することです。エージェントは、一度きりのタスクのために新しい依存関係や Factory、Strategy のレイヤーを持ち出しがちです。日付を1つフォーマットするためだけに日付ライブラリをインストールするのが典型例で、Intl.DateTimeFormat を使えばネイティブで実現できます。差分でファイル、パッケージ、抽象化が追加されている場合は、既存のコードでより少ないファイル数で済まないかを検討しましょう。済むのであれば、その変更を却下するか、より小さな変更を求めてください。
主導権を握り続けるための習慣
- エージェントに選択の理由を説明させる。 その説明の良し悪しを判断できないなら、次に学ぶべき基礎知識が見つかったということです。
- 受け入れる前に差分を読む。 毎回、すべてのハンクをレビューしましょう。
- 小さな変更は手で書く。 修正を自分でタイプすることで、理解を最新の状態に保てます。
テストを書けば、あなたの判断を、変更のたびに実行されるチェックに変えられます。
まとめ
コードを書くのはエージェントでも、そのコードの動作に責任を負うのはあなたです。HTTPのセマンティクス、ブラウザのレンダリングモデル、状態の所有権、セキュリティの境界、デバッグ、Git、そして物事を小さく保つ自制心を身につければ、悪い差分を却下し、その理由を説明できるようになります。次にエージェントから変更を渡されたら、まず git diff を読み、まだ説明できない判断を1つ見つけ出すところから始めてみてください。
よくある質問
axios にも fetch と同じHTTPエラーの問題はありますか?
いいえ。axios はデフォルトで、200〜299 の範囲外のステータスのレスポンスに対して Promise を reject するため、404 や 500 は catch ブロックで処理されます。この挙動は validateStatus オプションで制御されており、上書きすることも可能です。ただし、ボディにエラーメッセージを入れて 200 を返すAPIの場合は、axios でも fetch でも成功として resolve されるため、サーバーが正しいステータスコードを返す必要があります。
Vite の環境変数にAPIキーを入れても安全ですか?
変数に VITE_ プレフィックスが付いていない場合に限り安全です。Vite は VITE_ プレフィックス付きの変数を import.meta.env を通じてクライアントコードに公開し、ビルド時に静的に置換するため、その値はクライアントバンドルに含まれます。プレフィックスのない変数はクライアントコードには公開されません。Next.js の NEXT_PUBLIC_ と同様に、このプレフィックスは値を公開することを選択するものであり、値を保護するものではありません。シークレットは必ずサーバー上に置いてください。
textContent では不十分な場合、ユーザーが入力したHTMLを安全にレンダリングするにはどうすればよいですか?
挿入する前にHTMLをサニタイズします。OWASP は DOMPurify を推奨しています。文字列を DOMPurify.sanitize に通し、その結果を innerHTML または dangerouslySetInnerHTML で使用してください。サニタイズ後の出力をあとから変更してはいけません。変更によって危険なマークアップが再び混入する可能性があるためです。また、新たな回避手法が定期的に発見されるため、サニタイザーライブラリは常に最新のパッチを適用しておきましょう。これが必要になる典型的なケースは、リッチテキストエディタやユーザーが作成したMarkdownです。
開発環境で React の Effect が2回実行されるのはなぜですか?
開発環境では、React の Strict Mode がクリーンアップの欠けた Effect を検出するために、コンポーネントを追加で1回マウントします。これは本番環境では発生しません。2回目の実行でリスナー、リクエスト、サブスクリプションが重複して作成される場合、その Effect にはセットアップを元に戻すクリーンアップ関数が必要です。Strict Mode を外したり、ref によるガードを追加して2回目の実行をスキップしたりしても、バグが隠れるだけです。