第2章 実行と検証のループ

学習目標

  • ローカル実行、開発サーバー、ポートの関係を説明し、初めて受け取ったリポジトリを実行できるよう準備する。
  • テスト・型チェック・リント・ビルドが、それぞれ何を検出し、何を見逃すかを区別する。
  • 失敗時の出力から、何が、どこで、なぜ間違ったかを読み取り、最初に見るエラーを選ぶ。
  • 仮説を1つずつ確かめ、失敗の原因を絞り込む。
  • ブラウザでの確認と証拠の要求で「完了」の主張を検証し、検証できなかったことは明記する。

はじめに

「修正が完了しました。すべてのテストが通ります」。コーディングエージェントと一緒に働いていると、この文を1日に何度も目にします。しかし、テストが本当に実行されたのか、実行されたテストが今回の変更を対象にしているのか、画面でも問題が消えたのかは、この文だけではわかりません。

あるサイドプロジェクトの運営者は、エージェントの報告を信じてデプロイし、翌日の朝に問い合わせを受けました。テストは、本当にすべて通っていました。ただし、テストが対象にしていたのは計算関数だけで、修正したバグは画面にありました。ブラウザを一度でも開いてみれば、気づけたことです。

この章では、「できた」という言葉を「できた証拠」に変える技術を扱います。アプリがどこで動くかから始め、ローカル実行の準備、チェックツールの役割、エラーメッセージの読み方、原因を絞る手順を順に見ます。続いて、人が覚えていなくても検証ループを回す仕組み、ブラウザでの確認、証拠の要求、コンテキストの整理を扱います。

重要な概念

アプリが動く場所:ブラウザ、サーバー、データの保存先

Webアプリは通常3つの場所で動きます。ユーザーのブラウザは画面を描き、ボタンのクリックを受け取ります。サーバーはブラウザの要求を受け、計算して応答を返します。データの保存先(データベースやファイル)は、再読み込みしても残すべきデータを保管します。飲食店に例えると、ブラウザは客席、サーバーは厨房、データベースは倉庫です。

この区別が大切なのは、エラーが現れる場所が異なるからです。

問題の場所 エラーを見る場所
ブラウザ(画面のコード) ブラウザの開発者ツールのコンソール
ブラウザとサーバーの間(要求・応答) 開発者ツールのネットワークタブ、サーバーの出力
サーバーのコード 開発サーバーを起動したターミナル、デプロイ後はサーバーログ
データベース接続 サーバー出力の接続エラー(例:接続拒否を表すECONNREFUSED。データベースが停止しているか、アドレス・ポートが間違っている場合)

「画面に保存したと表示された」と「データが保存された」は別の事実です。保存は再読み込みするか、別の端末で開き直して確認します。

ローカル実行:開発サーバーとポート

ローカル実行は、アプリをインターネットに公開する前に、自分のコンピューターで動かすことです。正式開店前の家族向け試食会のようなものです。アプリを起動するプログラムを開発サーバーといい、番号付きの入口であるポート(port)を開いて要求を待ちます。http://localhost:3000は「自分のコンピューターの3000番の入口」という意味です。

起動時のEADDRINUSE(address already in use)は、そのポートをすでに別のプロセスが使っていることを表します。以前起動した開発サーバーが、別のターミナルで生きている場合がよくあります。対処はそのプロセスを止めるか、別のポートで起動することです。停止時はポートを使うプロセス番号(PID)を確認し(macOSとLinuxではlsof -i :3000)、kill <PID>でそのプロセスだけを終了します。名前のパターンでまとめて止めるコマンドは、無関係なプログラムまで止める危険があります。通常はポートの占有が原因なので、再インストールでは解決しません。まれに、同じサーバーを2回起動するコードや設定が原因の場合もあります。

初めて受け取ったリポジトリを実行できるようにする

他の人が作ったリポジトリや、エージェントが作ったリポジトリを初めて受け取ると、すぐにnpm run devから入力したくなります。しかし、実行できるのは、いくつかの準備が整っていてこそです。順番に確認すれば、迷う時間を減らせます。

  1. READMEと指示ファイルを読む。 実行コマンドと必要な準備が書かれています。
  2. ランタイムのバージョンを合わせる。 リポジトリに.nvmrcなどのバージョンを指定するファイルがあれば、そのバージョンのNode.jsを使います。
  3. ロックファイルどおりにインストールする。 package-lock.jsonがあれば、npm ciで記録されているバージョンをそのままインストールします。npmのドキュメントは、このコマンドはロックファイルがなければ動かず、package.jsonと内容が食い違う場合は、それに合わせて修正するのではなく、エラーを出して停止し、package.jsonもロックファイルも変更しないと説明しています。
  4. 環境変数を設定する。 .env.exampleで必要な名前を確認し、.env.localなどのローカルファイルにテスト用の値を入れます。
  5. データベースを準備する。 マイグレーションコマンドがあれば実行し、テーブルを作ります。
  6. 開発サーバーを起動し、主要な画面を1つ自分で開く。

これらの段階を飛ばすと、現れるエラーはたいてい決まっています。環境変数がないというエラーは第4段階を、Cannot find moduleは第3段階を、データベースへの接続拒否は第5段階を見直すようにという合図です。

チェックツールの役割

コードを確認する自動チェックにはいくつかの種類があり、それぞれ検出するものが異なります。

ツール 例え 検出する問題 検出できない問題
テスト 試食係の味の評価 指定した入力に対して期待どおりの結果が得られるか テストを書いていない動作
型チェック 設計図の寸法の照合 関数に渡す値の種類と個数、存在しない属性の使用 値の形式は正しいが計算ロジックが誤っている場合
リント 厨房の衛生チェック表 未使用変数、危険なコードのパターン、ルール違反 機能が要件どおりに動くか
ビルド 開店前の最終組み立て 配布用にまとめるときだけ現れるエラー 実行中に発生する論理エラー

TypeScriptでは、tsc --noEmitのように実行ファイルを作らず型チェックだけを行うコマンドを使います。TypeScriptのドキュメントは、noEmitをコンパイル結果を出力せず、TypeScriptを型チェッカーとして使うオプションと説明しています。関数の必須引数を1つ増やした場合、型チェック1回で合わなくなった呼び出し元を見つけられます。ただし、anyなどで型チェックを避けた呼び出しは見逃す場合があります。

大切なのは、ツールごとに「通過」の意味が異なることです。テストが通っても対象外の画面まで正しいとは限らず、型チェックが通っても割引率の計算が正しいとは限りません。変更の性質に合うチェックを選び、軽いものから実行します。

エラーメッセージを読み解く

エラーメッセージは怖がらせるために長いのではありません。たいてい3つのことを伝えています。

  1. 何が:エラーの種類(TypeError、失敗したテスト名)
  2. どこで:ファイル名と行番号
  3. なぜ:期待値と実際の値、または不足していたもの
TypeError: Cannot read properties of undefined (reading 'filter')
    at ClassSchedule (src/components/ClassSchedule.tsx:31:18)
    at renderWithHooks (node_modules/react-dom/cjs/react-dom.development.js:15486:18)

1行目は「まだ値がない(undefined)対象にfilterを使おうとした」という意味です。その下のスタックトレース(stack trace)は、エラーが起きるまでに経由した関数を内側から並べたものです。node_modules内の行は他の人が作ったライブラリで、src/内の行が自分のコードです。多くの場合、自分のコードのうち最も上にある行、この例ではClassSchedule.tsxの31行目から開きます。データを読み込む前に一覧を描画しようとした可能性が高いです。

何十ものエラーが一度に出たときは、いちばん最初のエラーから見ます。1つのモジュールが見つからなければ、そのモジュールを使うすべての場所で連鎖的にエラーが起きるからです。最初の原因を直すと、その後のエラーの大半も一緒に消えます。

コマンドの成功と失敗を最も確実に示すのは、文言ではなく終了コード(exit code)です。終了コード0は成功、0以外の値は失敗を意味します。警告が大量に表示されても終了コードが0ならコマンドは成功しており、最後の行に何もエラーが表示されなくても、0以外なら失敗です。直前のコマンドの終了コードは、ターミナルでecho $?と入力して確認します。また、プログラムは通常、結果を標準出力(stdout)に、エラーを標準エラー出力(stderr)に分けて書きます。出力が長く、そのまま全部をエージェントに渡すときは、npm test > test.log 2>&1のように標準出力をファイルに送り(>)、標準エラー出力も同じ場所にまとめる(2>&1)と、漏れなく渡せます。

失敗時の出力から原因を絞り込む

原因を探すのは、探偵の仕事と同じです。手がかりを集め、容疑者を1人ずつ確認し、確かめられていない推測で人を逮捕することはしません。

  1. 再現する。 同じ手順で同じ失敗になるかを確認します。再現できなければ、直ったかもわかりません。
  2. 失敗した段階を区別する。 インストール、起動、ビルド、実行中のどこで失敗したかによって、調べる場所が異なります。
  3. 最初の関連エラーを選ぶ。 連鎖したエラーは後回しにします。
  4. 仮説を1つ立てる。 「このimportのパスが変わった」のように、確認できる文にします。
  5. その仮説だけを確かめる。 同時に複数箇所を直すと、何が効いたかわかりません。
  6. 修正後、同じチェックを再実行する。 最初に失敗した、そのコマンドで確認します。

エラーの修正をエージェントに任せるときは、出力全体を貼り、「原因を直し、エラーを隠さない」と伝えます。Claude Codeのベストプラクティスも、ビルドの失敗を任せる際はエラー原文を添え、成功を確認させ、エラーを抑え込まず根本原因を直すよう指示することを勧めています。隠す典型例は、型チェックを無効にするコメント、テストのスキップ、期待値を実際の値に合わせる変更です。期待値が要件と一致するなら、間違っているのはコードです。

検証ループと強制の仕組み

検証ループは、修正、チェックの実行、結果の読み取りを、チェックが通るまで繰り返す流れです。チェックコマンドを教えれば、エージェントはこのループを自分で回しますが、人が毎回伝える必要があり、エージェントがときどき忘れることもあります。そのため、ループの一部を仕組みとして固定します。

仕組み 役割 例
作業指示 今回実行するチェックを伝える 「実装後にnpm testを実行し、出力を見せて」
指示ファイル 全作業共通のチェックコマンドを伝える 「作業が終わったらnpm run check」
フック 決まった時点で例外なくチェックを実行する ファイル修正ごとのフォーマッター、終了前の検査スクリプト
CIの必須チェック 通らない変更をマージさせない プルリクエストごとにテストし、失敗すればマージボタンを無効にする

Claude Codeのドキュメントは、フックは指示ファイルと異なり決定的に動き、その行動の実行を保証すると説明しています。例えば、PostToolUseイベントにEdit|Write条件を付けると、その編集ツールでファイルを直した直後ごとにフォーマッターを実行します。シェルコマンドによる変更は対象外なので、漏れなく見るには終了時にもチェックします。プロジェクトの.claude/settings.jsonに書いてコミットすれば、チームで共有できます。対話セッションでは、そのフォルダを信頼すると承認して初めてフックが動き、設定で無効にもできます。Stopフックは応答を終えようとするとき検査スクリプトを実行し、失敗すれば作業を続けさせます。ただしドキュメントによると、ツール呼び出しなしに8回連続で引き止めると、その次は終了を認めるため、必ず通る保証はありません。

GitHubの保護ブランチ(protected branch)で「マージ前にステータスチェックの通過を必須にする」を有効にすると、指定の必須チェックが成功(またはスキップ・中立)で終了しなければマージできません。レビュアーが忘れても、壊れた変更は入りません。既定では管理者は適用除外なので、例外なく適用するなら迂回を禁止するオプションも有効にします。CI(継続的インテグレーション。変更ごとに自動チェックを行うシステム)ではnpm ciで依存関係をインストールします。npmも、このコマンドをCIなどの自動化環境用と説明しています。

ブラウザでユーザーのように確かめる

画面が変わる作業は、ブラウザで自分で確認して初めて完了します。Anthropicの長時間実行エージェントの実験でも、エージェントは機能を十分に試さずに完了と記す傾向がありました。そこで、ブラウザ自動化を使って人間のユーザーのように試すよう明示すると、コードを読むだけでは見逃していたバグを発見しました。Claude Codeのベストプラクティスも、画面を変更した場合は、結果の画面をキャプチャしてデザイン案と比較し、違いを修正させるよう勧めています。

直接確認するときは、次を見ます。

  • ボタンを押し入力する流れを最後まで実行し、再読み込みしても結果が残るか
  • スマートフォンの画面幅(例:360px)に狭めたとき、切れたり重なったりしないか
  • 空のデータ、不正な入力、遅い応答など、第1章で決めた状態
  • 開発者ツールのコンソール:赤いJavaScriptエラーがないか
  • 開発者ツールのネットワークタブ:実際に要求が送られたか、応答のステータスコードは何か

ステータスコードは応答結果を数字で要約します。200番台は成功、400番台は要求側の問題(不正な入力、権限なし、存在しないアドレス)、500番台はサーバー側の問題です。ボタンを押しても反応がなく、ネットワークタブに要求自体がなければ画面のコードを、要求が500で失敗したならサーバー出力を見ます。

主張と証拠を区別する

エージェントの「完了しました」は主張です。証拠は、実行したコマンドとその元の出力、画面キャプチャ、確認手順と結果です。Claude Codeのベストプラクティスは、成功を断言させるのではなく、テストの出力、実行したコマンドとその結果、結果の画面などの証拠を示させるよう勧めています。証拠のレビューは、検証をもう一度実行するより速く、見守っていなかったセッションにも使えるからです。

良い完了報告には、確認したこととともに、確認できなかったことも含まれます。

実行したチェック:npm test(42件通過、0件失敗)、npx tsc --noEmit(エラーなし)
画面確認:モバイル幅・デスクトップ幅のキャプチャ添付。再読み込み後も保存を維持
確認できなかったこと:実際のSMS送信(テストでは送信記録のみ確認)

同じコンテキストで働いたエージェントは、同じ思い違いを繰り返して「確認」してしまう場合があります。そのためClaude Codeは、作業した本人が自己採点しないよう、新しいコンテキストのサブエージェントに結果への反証を試みさせる方法を紹介しています。

コンテキストが混乱したら、やり直す

第1章で見たように、会話のコンテキストには失敗した試みも蓄積します。同じ問題を何度も直すと、失敗した進め方の蓄積が同じミスを招きます。Claude Codeのベストプラクティスは、同じセッションで同じ問題を2回を超えて修正しても解決しない場合は、/clearでコンテキストを空にし、学んだことを含む、より具体的な指示で始め直すよう勧めています。まっさらなセッションと改善した指示は、訂正の積み重なった長いセッションより、ほぼ常に優れるということです。

始め直す前に2つ覚えておきます。第一に、会話を消してもディスクのコードはそのままです。失敗した変更が残っていれば、最後に検証したコミットに戻すかを先に決めます(第3章)。第二に、新しいセッションへの引き継ぎメモを残します。試したこと、失敗したこと、否定できた仮説を書けば、同じ道を繰り返さずに済みます。

会話を捨てるのが惜しければ、/compact <指示>で、指定した内容に焦点を当てて要約します。

さらに深く

通るテストと役に立つテスト

テストが通るという事実は、そのテストが役に立つことの証拠にはなりません。役に立たないテストは、バグがあっても通ります。代表的なケースは4つあります。

役に立たないテスト 内容 確認方法
実装を写したテスト コードと同じ方法で期待値を計算する 期待値が実装とは独立に、要件から導かれているかを確認する
弱すぎるアサーション 「結果が存在する」だけを確認する 具体的な値や状態を比較しているか見る
一度も失敗していないテスト 修正前にも通っていた 修正コードを一時的に戻し、失敗するか見る
結果に合わせて直したテスト 失敗すると期待値を変えた diffでテストの期待値変更を探す

最も強力な確認方法は、先に失敗を見ることです。バグを修正するときは、まず再現するテストを書き、修正する前にそのテストが失敗することを確認します。Claude Codeのベストプラクティスも、症状を説明したあとに「再現する失敗テストを書き、そのあとで直してください」と任せる例を挙げています。修正前に失敗し、修正後に通ったテストだけが、その修正を証明します。

似た落とし穴に、スナップショットテスト(画面や出力の以前の状態を保存しておき、比較するテスト)の一括更新があります。失敗したスナップショットを一度に「新しい基準」として上書きすると、意図した変更と意図しなかった変更が、どちらも正解になってしまいます。更新する前に、失敗したスナップショットを1つずつ確認し、それが今回の作業で変えようとしていたものかを確かめます。

ときどき失敗するテスト

コードを変えていないのに通ったり失敗したりするテストを、不安定なテスト(flaky test)と呼びます。再実行して通れば先に進みたくなりますが、たいてい本当の原因があります。

  1. テスト同士が状態を共有している。 同じテスト用データベースやグローバル変数を使うと、前のテストが残したデータが後のテストを壊します。単独では通るのに、全体を実行すると失敗するのが代表的な合図です。Playwrightのドキュメントは、テストごとに完全に分離され、独立して実行されることで、再現性が高まり、失敗の連鎖を防げると強調しています。
  2. 待機する方法が間違っている。 画面の要素が遅れて現れるのに、「存在するか」を1回だけ確認すると、その瞬間によって結果が分かれます。PlaywrightのWeb優先アサーション(await expect(locator).toBeVisible())は、条件が満たされるまで待って再試行しますが、isVisible()で直接確認すると、待たずにすぐ結果を返します。固定の時間だけ待機する方法では、遅い環境では依然として失敗し、速い環境では時間を無駄にします。
  3. 外部に依存する。 実際の外部APIや現在時刻で結果が変わります。テストでは応答と時刻を固定します。

再試行の設定は症状を隠すだけで、原因を取り除くわけではありません。CIでときどき失敗するテストは、失敗したときの情報がなければ修正できません。そのため、Playwrightのドキュメントは、CIでの失敗の分析には動画や画面キャプチャよりもtrace viewer(記録した実行過程を段階ごとにたどり直すツール)を使い、失敗したテストを最初に再試行するときにtraceを残すよう設定することを案内しています。

自分のコンピューターと別の環境で結果が違うとき

「自分のコンピューターでは動くんですが」は、開発現場で最も古い冗談です。結果が異なるなら、2つの環境の違いが原因です。その違いは、たいてい次のいずれかです。

違い 例 合わせ方
ランタイムのバージョン 自分のコンピューターとCIでNode.jsのメジャーバージョンが違う バージョンファイルを置き、両方を合わせる
インストールした依存関係 npm installで別の下位バージョンが入った ロックファイルをコミットし、CIではnpm ci
環境変数 値が自分のローカルファイルにしかない 必要な名前を文書化し、CI・デプロイ設定にも入れる
OS macOSのAPFSは、大文字と小文字を区別する形式を別に選ばなければファイル名の大文字と小文字を区別しないが、Linuxは区別する importパスの大文字と小文字を、実際のファイル名と正確に合わせる
実行方法 開発サーバーだけで確認し、配布用ビルドをしていない デプロイ前にローカルでもビルドする

環境変数には特に落とし穴があります。Next.jsによると、.env*.localは.gitignoreでリポジトリから除外するのが原則で、テスト環境(NODE_ENV=test)は.env.localを読みません。全員のテスト結果をそろえる設計なので、自分の.env.localに依存するテストが別の環境で失敗するのは、仕様上当然の結果です。

性能も同様です。開発サーバーは配布用ビルドと異なる実行をするため、修正前後を、本番に近い同じ条件で測ってこそ比較できます。

実務の場面

塩パン屋:保存できない午前9時

町のパン屋「塩パン屋」の予約アプリで、お客さまから「時間を選んでも保存できない」と連絡がありました。運営者ミンさんは、修正前に症状を再現しました。午後なら保存できるのに、午前9時台では保存ボタンを押しても反応がありません。コンソールにはエラーが出ておらず、ネットワークタブにはリクエストに対してステータスコード400が返された記録がありました。画面ではなく、リクエスト内容の問題でした。

ミンさんは再現手順をエージェントに渡し、「この症状を検出するテストを先に追加し、修正する前に失敗するところを見せて」と頼みました。エージェントはすぐにフォーマット検証のコードを直すことを提案しましたが、ミンさんは断りました。先に失敗を確認しなければ、修正後にテストが通っても、その結果を信頼できなかったからです。テストは予想どおり失敗し、出力にはExpected: "09:00" / Received: "9:00"と表示されました。時刻文字列の先頭の0が抜けており、サーバー側のフォーマット検証に引っかかっていたのです。

エージェントが時刻を作る関数を修正すると、新しいテストが通りました。エージェントは「完了」と報告しましたが、ミンさんはそこで止まりませんでした。全体のテストと型チェックを実行し、ブラウザで午前9時を選んで保存しました。そのあと、ネットワークタブで要求が200で成功したことと、再読み込み後も予約が残っていることを確認しました。そこまで確認して、初めてコミットしました。

症状を再現したからこそテストを書くことができ、修正する前に失敗を見たからこそそのテストを信頼でき、ブラウザで確認したからこそ症状が消えたと言うことができたのです。

みどり星植物園:47個のエラーを前に

会員用の植物管理アプリを運営する「みどり星植物園」の開発者チさんは、エージェントに、点在している植物の補助関数をsrc/lib/plants/フォルダに整理するよう任せました。作業が終わってからnpm run buildを実行すると、47個のエラーが一度に出ました。

エージェントは2つの方法を提案しました。1つはエラーが出た行ごとに型チェックをスキップするコメントを付ける方法で、もう1つはフォルダ整理を丸ごと元に戻す方法でした。チさんは、どちらも断りました。1つ目の案はエラーを隠すだけで原因をそのまま残し、2つ目の案は依頼した整理作業まで捨ててしまうからでした。

チさんは出力を最初から読みました。いちばん最初のエラーは、src/app/watering/page.tsxでのCannot find module '@/lib/plant-utils'でした。その後のエラーの大半は、このモジュールから取り込んでいた関数と型が見つからず、それらを使うファイルごとに続けて発生した、連鎖的なエラーに見えました。チさんは仮説を1つ立てました。「古いパスから取り込んでいるファイルがまだ残っている」。そして、その仮説だけを確認するように任せました。

古いパス '@/lib/plant-utils' を取り込むファイルをすべて探し、新しいパスに変える。
ほかのコードは変えない。変更後 npm run build を再実行し、出力全体を示す。

もう一度ビルドを実行すると、エラーは2つに減りました。残った2つは別の原因によるものでした。整理の過程で、水やりの計算関数が季節の値を追加で受け取るように変更されていたのに、その関数を呼び出す2か所は以前の方式のままでした。チさんは、この変更が依頼した範囲に含まれるかを検討しました。フォルダ整理しか任せていないので、関数の形を変えることは範囲外でした。関数を元の形に戻してもらうと、ビルドが通りました。

チさんは続けて型チェックとテストを実行し、ブラウザで水やりの予定画面を開いて、日付が以前と同じかを確認しました。47個のエラーの原因は2つでした。最初のエラーから始め、仮説を1つずつ確認したおかげで、エラーを隠すことも、変更を捨てることもなく修正できました。

間違えやすいポイント

  1. 「テストが通ったので終わりだ。」 テストの通過は終わりに見えます。しかし、テストが確認するのは、そのテストが対象とする範囲だけです。画面を変えたのに画面のテストがないなら、ブラウザで確認するまでは終わりではありません。
  2. 「失敗するテストは期待値を直せばよい。」 数字を1つ変えるだけなので、すぐに済みます。しかし、期待値が要件と一致しているなら、間違っているのはコードのほうです。エージェントがテストを変更したり、スキップしたりすることを提案したときこそ、最も注意が必要です。
  3. 「再実行して通れば大丈夫だ。」 再実行で通ると安心します。しかし、ときどき失敗するのには、たいてい状態の共有や誤った待機方法などの原因があり、毎回同じ値で失敗するなら、ロジックの誤りです。再実行は原因の分析の代わりにはなりません。
  4. 「エラーが多ければ、問題も多い。」 数が大きいと途方に暮れます。しかし1つの原因から何十ものエラーが連鎖することは多いです。最初のエラーから見ます。
  5. 「エージェントが確実だと言った。」 自信のある報告は信頼感を与えます。しかし、自信は証拠ではありません。コマンド、出力、画面などの証拠を求め、確認できなかったことは何かを尋ねます。
  6. 「会話を消せば、作業も最初に戻る。」 新しいセッションは新たな出発のように感じます。しかし、コンテキストを空にしても、ディスク上のコードの変更は残ります。元に戻すかどうかは、Gitを使って別に決めます。

ひと目でわかるまとめ

話題 覚えておくこと
エラーの場所 画面はコンソール、要求はネットワークタブ、サーバーはターミナル・ログ
ポートの競合 EADDRINUSEは、ポートをすでに他のプロセスが使用しているという意味。PIDを確認し、そのプロセスだけを止める
初回実行 README → ランタイムのバージョン → npm ci → 環境変数 → マイグレーション → 実行
チェックツール テストは動作、型は形式、リントはパターン、ビルドは配布用の組み立て
エラーの読み方 何が・どこで・なぜ。自分のコードの最上行、最初のエラー、終了コード
原因の絞り込み 再現 → 段階の区別 → 最初のエラー → 仮説1つ → 確認 → 同じチェックの再実行
強制の仕組み 指示 < 指示ファイル < フック < CIの必須チェックの順で強い
ブラウザ 流れ全体、狭い画面、再読み込み後の維持、コンソールとステータスコード
証拠 コマンドと元の出力、キャプチャ、確認できなかったことも含める
コンテキスト整理 2回を超えて訂正しても直らなければ/clearと改善した指示。コードは別途戻す

公式ドキュメントで確認する

セルフチェック

2-1. 開発サーバーを起動すると、Error: listen EADDRINUSE: address already in use :::5173と表示されました。何を意味し、どう対処するかを説明してください。また、対処するときに避けるべき方法も1つ挙げてください。

2-2. エージェントは「テストの実行結果は失敗0件」と報告しました。出力を開いてみると、Tests: 0 passed, 0 totalでした。この結果を通過として受け入れてはいけない理由と、最初に疑うべき原因を説明してください。

2-3. 問い合わせフォームの「送信」ボタンを押しても、何も反応がありません。ブラウザの開発者ツールのコンソールとネットワークタブで、それぞれ何を確認し、その結果から問題の場所をどう区別するかを説明してください。

2-4. 次の出力で最初に開くファイルと行、および理由を説明してください。

TypeError: Cannot read properties of null (reading 'price')
    at sumLines (src/checkout/summary.ts:47:12)
    at OrderPanel (src/components/OrderPanel.tsx:18:9)
    at renderWithHooks (node_modules/react-dom/cjs/react-dom.development.js:15486:18)

2-5. ビルドコマンドの出力に黄色い警告が何十行も表示され、最後の行にはエラーの文言が何もありません。このコマンドが成功したかを最も確実に確認する方法を説明してください。

2-6. エージェントが割引計算のバグを修正するときに、新しいテストを1つ追加し、そのテストは通っています。このテストが本当にそのバグを検出するかを確認する、簡単な方法を説明してください。

2-7. MacBookで動くアプリが、LinuxのCIだけでModule not found: Can't resolve './components/header'で失敗します。実際のファイル名はHeader.tsxです。原因と修正方法を説明してください。

実習課題

実習

失敗時の出力から一巡する(45分)

  1. 練習用アプリの関数1つに、わざとバグを入れる(例:合計の計算で最後の項目が抜けるようにする)。
  2. そのバグを検出するテストを先に書き、失敗を自分で確認する。
  3. 失敗時の出力から、何が・どこで・なぜ間違っているかを紙に書く。
  4. 出力全体をエージェントに貼り付け、「原因を直し、テストは変えずに、全体のテストの出力と終了コードを見せてほしい」と任せる。
  5. 修正コードを一時的に戻してテストの再失敗を確認し、修正状態に戻す。

完了基準: 自分が書いた原因とエージェントが修正した箇所が一致し、修正前の失敗時の出力と修正後の通過時の出力(終了コード0)が両方残っており、テストファイルには変更がない。

実習

検証を仕組みとして固定する(50分)

  1. 指示ファイルに、作業を終えるときに実行するチェックコマンド(例:npm run check)を1行書く。
  2. ツールのフックで、ファイル修正ごとにフォーマッターかリントが動くよう設定し、その設定ファイルをコミットする(Claude Codeなら.claude/settings.jsonのPostToolUse)。
  3. エージェントに小さな画面の変更を任せたあと、フックが実際に実行された痕跡を確認する。
  4. ブラウザでスマートフォンの画面幅とデスクトップの画面幅で結果を確認し、コンソールにエラーがないかと、ネットワーク要求のステータスコードを記録する。
  5. 報告を「実行したチェック、画面確認、確認できなかったこと」の3欄に整理する。

完了基準: コミット済みのフック設定があり、エージェントが別途指示されなくてもフックが実行された記録が残っている。また、3欄からなる完了報告で「確認できなかったこと」の欄が空ではないか、空である理由が書かれている。