Givery教材トップシステムの歩き方事前セットアップ
DAY 3 2026年9月17日(木)

壊れたときに効く動き

壊れて止まったものと、遅くなったものを前にして、原因を絞ってから直す動きを通します。見るのはコードそのものより、出たエラーとログです。どちらも読み方は言語を選びません。自分の現場で明日から同じ順番で動ける状態を目指します。

この日の演習

演習は上から順に進みます。全員が終わるのを待ってから次へ進むので、詰まったら手を挙げてください。早く終わった方には各演習に発展課題があります。

D3-1止まらない処理の原因を突き止める[30min]
ねらい

エラーの山から原因の1行を絞り込みます。コードを読む前に、エラーの形から当たりをつけます。

この演習で扱うもの

扱うのは CrashingOrderProcessor という、受注をまとめて処理するバッチです。5件の受注を順に処理し、失敗したら決まった回数だけやり直します。

5件のうち2件は必ず失敗するようになっています。金額が100万円を超えると外部システムとの連携が要る作りで、演習環境には外部システムがないためです。つまり、失敗すること自体は想定内です。

問題は、失敗したあとのやり直しが止まらないことです。実行すると大量のエラーが出て、プログラムが落ちます。この題材は Java ですが、同じ壊れ方はどの言語でも起きます。

なぜこれをやるのか

エラーが数百行出たとき、全部読む人はいません。読まずに人に聞くか、読もうとして時間を溶かすか、どちらかになります。

AI に貼れば答えは返ります。ただ、貼る前に当たりをつけられるかどうかで、返ってきた答えを評価できるかが変わります。「たぶんここだろう」を持っていれば、違う答えが返ったときに気づけます。

今日やるのは、エラーの読み方を2つ覚えることです。同じ行が繰り返されていたらそこが原因。数字が急に変わっていたらその手前が原因。この2つは言語を選びません。

ここまで出たら完了
  • エラーを全部読まずに、繰り返されている名前から原因を言えた
  • === バッチ処理終了 === が出て、最終行が 成功: 3件, 失敗: 2件 になる
  • BATCH-004 と BATCH-005 で リトライ残り: 3回 / 2回 / 1回 の3行が出て打ち切られる
  • 承認前の差分で、変更が1行だけに収まっていることを確かめた
完了時。リトライが3回で打ち切られ、最終行が 成功: 3件, 失敗: 2件 になります
完了時。リトライが3回で打ち切られ、最終行が 成功: 3件, 失敗: 2件 になります
さわるファイル
書くexercises/day3/CrashingOrderProcessor.java再帰の引数1行だけ直す
読むexercises/day3/exercise1-デバッグ.md手順と模範プロンプト
まず自分で考える [5min]

まず実行して、出たエラーを見てください。読み込む必要はありません。上から5行と、繰り返されている名前だけ見ます。そのうえで3つ答えてください。

  • 同じ名前の行が何度も出てくる。その名前は何か
  • そのメソッドは、どういうときに自分をもう一度呼ぶ作りになっているか
  • やり直しの回数は、呼ぶたびに減っているか。減っていないとしたら何が起きるか
AI と詰める

当たりをつけたら、AI に聞いて答え合わせをします。自分の見立てを先に書いて送ると、合っているかどうかが返ります。

ここで「直してください」と書かないのがポイントです。原因を確かめてから直すほうが、何を直したかが残ります。

@exercises/day3/CrashingOrderProcessor.java
実行すると StackOverflowError が出ます。
エラーを見た限り processWithRetry が繰り返し呼ばれているようです。
この見立ては合っていますか。原因を教えてください。まだ直さないでください。
手を動かす
1
考える
processWithRetry を開き、この再帰がどこで止まる想定なのかを1行メモします。retryCount が呼び出しごとにどう変わるかも書き添えます。
2
実行
VSCode で配布フォルダ(pom.xml がある階層)を開き、ターミナルで cd exercises/day3 のあと javac -encoding UTF-8 -d . CrashingOrderProcessor.java と java -Dfile.encoding=UTF-8 -cp . com.example.order.debug.CrashingOrderProcessor を打ちます。リトライ残り: 3回 が数千行流れたあと、*** StackOverflowError が発生しました *** に続けて java.lang.StackOverflowError から始まるスタックトレースが出ます。
3
書く
claude を起動し、@CrashingOrderProcessor.java 実行すると StackOverflowError が出ます。原因はどこですか、とだけ送ります。返答を見てから リトライは最大3回で打ち切る仕様です を足し、java.lang.StackOverflowError の行と processWithRetry が並ぶ部分を20行ほど指示文の末尾に貼ります。
4
答え合わせ
承認前に出る差分で、processWithRetry(order, retryCount - 1); が入っているか、変更行が1行だけかを見てから承認します。もう一度コンパイルして実行し、確認できたら cd ../.. でプロジェクト直下に戻ります。
言葉にしてみる [1min]

手が止まったら、隣に説明するつもりで声に出してみてください。詰まったところが、まだ入っていないところです。

  • 何が原因で止まっていたか。専門用語を使わずに言えるか
  • その原因に、どうやって気づいたか(何を見て分かったか)
  • 同じことが自分の現場で起きたら、最初に何を見るか
考えること
リトライを再帰で書くのとループで書くのとでは、このバッチにはどちらが向くか。
AI の出方
原因を当てても、修正案が for ループへの全面書き換えになったり、try の位置ごと動かしてきたりします。差分に retryCount - 1 が入っていなければ、再帰に渡す引数を1つ減らす形だけで直して、と言い直してください。
D3-1+maxRetries を 3 から 0 に変えて保存し、javac -encoding UTF-8 -d . CrashingOrderProcessor.java を打ち直してから java -Dfile.encoding=UTF-8 -cp . com.example.order.debug.CrashingOrderProcessor を実行します。コンパイルし直さないと古いクラスファイルが動いて表示が変わりません。リトライ表示が1行も出ないまま失敗2件に入るかを、予想を先に書いてから確かめると、境界の効き方を自分の言葉で説明しやすくなります。確認したら 3 に戻し、同じくコンパイルからやり直します。
くわしく(背景・詰まったときの対処)

配布フォルダの exercises/day3 に、受注バッチを模したクラスが1本だけ置いてあります。Spring Boot 本体からは切り離した単体実行用で、package com.example.order.debug を宣言しています。金額が100万円を超える BATCH-004 と BATCH-005 は外部連携で必ず失敗する作りです。ここでリトライが止まらなくなります。

StackOverflowError は、メソッド呼び出しが深くなりすぎて呼び出しスタックが溢れたときに出る実行時エラーです。このクラスの main はそれを受け止め、見出しを1行出したあと e.printStackTrace() でスタックトレースを標準エラーへ流します。at com.example.order.debug.CrashingOrderProcessor.processWithRetry の行が1000行あまり並び、最後にヒントが2行出ます。JVM が保持するフレームは既定で1024までなので、実際の再帰回数はこれより多いです。Claude Code へ渡す証拠は、標準出力に流れるリトライ表示ではなく、この標準エラーのスタックトレースです。想定しているのは、夜中に落ちたバッチの調査で、朝に残っているのがこの出力だけ、という場面です。当たりを付けるところを Claude Code にやらせて、確認は自分の手でやります。

コンパイルは通るのに ClassNotFoundException や NoClassDefFoundError が出るときは、javac の -d . を付け忘れています。クラスファイルは exercises/day3/com/example/order/debug/ の下に出ます。新しいファイルは作りません。クラス名・パッケージ・メソッドの引数はそのままにして、判定に関わる1行だけを直してください。演習が終わったら cd ../.. でプロジェクト直下、つまり pom.xml がある階層に戻ります。mvn は pom.xml と同じ階層で打たないと、対象のプロジェクトを見つけられません。

D3-2遅くなった原因を2つに切り分ける[30min]
ねらい

ログの数字の動きから原因を切り分け、対策がどこに効くかまで説明できる状態にします。

この演習で扱うもの

扱うのは application-slow.log という50行のログです。ある日の朝9時から昼までの記録で、同じシステムが時間とともに遅くなっていく様子が入っています。

見るのは3種類の行です。応答完了の行に出ている処理時間。SQL を発行した記録。それと GC という、使い終わったメモリを片づける処理の記録です。

最初は156ミリ秒で返っていたものが、3時間後には9200ミリ秒かかり、最後は落ちます。データ件数も増えていますが、増え方より遅くなり方のほうが急です。ここに原因があります。

なぜこれをやるのか

遅いという報告を受けたとき、「遅いですね」で終わらせないための演習です。どの処理が、どういう条件で、いつから遅いのか。ここまで切り分けて初めて対策が決まります。

AI にログを貼れば、それらしい分析は返ります。ただ、根拠のない指摘も混ざります。「N+1問題です」と言われたとき、それがログの何行目から言えるのかを確かめられないと、見当違いの対策に時間を使うことになります。

今日は、コードを直しません。原因を名指しして、対策の効き先を分けて言えるところまでです。

ここまで出たら完了
  • 自分で見つけた3つと、AI の指摘のどこが一致し、どこが違ったかを言える
  • analysis-slow-log.md に、根拠として使った行番号が書けている
  • 原因が2つに分かれていて、片方は応答時間に、もう片方はメモリに効くと書き分けられている
  • 根拠を示せなかった指摘を、1つ以上落とした
完了の一例。ここまで書き込む必要はありません。行番号と、対策が2方向に分かれていれば達成です
完了の一例。ここまで書き込む必要はありません。行番号と、対策が2方向に分かれていれば達成です
さわるファイル
読むexercises/day3/application-slow.log劣化の根拠を時系列で拾う
読むexercises/day3/exercise1-デバッグ.mdD3-2 の手順と模範プロンプト
新規exercises/day3/analysis-slow-log.md原因と対策のメモを残す
まず自分で考える [7min]

ログを開いて、数字の動きを追ってください。コードは見ません。3つ見つけたらこの段は終わりです。

  • 応答完了の行にある処理時間を並べる。156ms から始まって、どこまで伸びるか
  • 同じ形の SQL が短い間隔で何本も並んでいる箇所を探す。何行目から何行目か
  • heap usage という数字を探す。上がりっぱなしか、下がっているか
AI と詰める

自分が見つけた3つを書いて渡し、分析させます。「コードは変更しないでください」を必ず添えてください。書かないと直しにいきます。

返ってきたら、根拠が曖昧なところに「それはログの何行目から言えますか」と聞き返してください。答えられない指摘は落とします。ここが今日の主眼です。

@exercises/day3/application-slow.log
ログを見て、次の3つに気づきました。
(自分が見つけた3つを書く)

遅くなっている原因を、ログの行番号を根拠にして説明してください。
コードは変更しないでください。分析だけお願いします。
手を動かす
1
考える
application-slow.log を VSCode で開き、応答時間が 156ms から 9200ms まで伸びる 5・14・23・35・39 行目と、order_items への SELECT が5行続く 30〜34 行目の行番号をメモに控えます。OutOfMemoryError が出た 48〜49 行目のメソッド名も控えます。
2
書く
claude を起動し、@application-slow.log を付けて、分析だけ、コードは変更しない、と添えてから劣化の原因と対策を時系列の根拠つきで出させます。
3
答え合わせ
自分のメモと返ってきた指摘を並べ、N+1 の解消と全件取得の見直しという2方向に分かれているかを読みます。根拠が曖昧なところは、どのログ行が根拠か、と聞き返します。
4
書く
exercises/day3/analysis-slow-log.md を作り、根拠にした行番号、対策2方向、それぞれが応答時間とメモリのどちらに効くかを箇条書きで残します。
言葉にしてみる [1min]

書いたメモを見ずに言ってみてください。見ないと言えないなら、まだ自分のものになっていません。

  • 遅くなっていた原因は2つ。それぞれ一言で何か
  • その2つは、どちらを先に直すべきか。理由は
  • 自分が同じログを渡されたら、最初にどの列を見るか
考えること
対策を1つしか入れられないとしたら、N+1 の解消とページネーションのどちらを先に打つか。
AI の出方
分析だけを頼んでも、途中からコードを直しにかかることがあります。指示文の冒頭に分析だけと置き、それでも編集の許可を求めてきたら断ってください。根拠が曖昧な回答には、どのログ行が根拠か、と聞き返してください。ログ行を示せない指摘は落とせます。
D3-2+プロジェクト直下(pom.xml がある階層)で作業します。src/main/java/com/example/order/service/OrderServiceImpl.java の cancelOrder を開き、SHIPPED を弾く if の直後に、同じ InvalidOrderStateException を投げる DELIVERED 用の if を1つ足します。メッセージには キャンセルできません を残してください。既存の SHIPPED のテストがこの文言を見ているので、外すと落ちます。mvn test -Dtest=OrderServiceImplTest を流し、DELIVERED のテストが PASS に変わるところで合否を判定します。API でも見る場合は、Java を書き換えたので起動中なら Ctrl+C で止め、mvn spring-boot:run を打ち直してから、DELIVERED の id=6 ORD-20260404-006 に PATCH /api/orders/6/cancel を打ちます。応答は、例外を HTTP ステータスへ変換する @RestControllerAdvice が未実装なら 500、D1-5+ で入れていれば 400 です。確認できたら Ctrl+C で止めておきます。8080 を掴んだままだと D3-3 の起動が失敗します。
くわしく(背景・詰まったときの対処)

application-slow.log は本番想定で取った Spring Boot のログです。GET /api/orders の応答が 156ms、880ms、3350ms、6100ms、9200ms と伸び、最後は OutOfMemoryError で止まっています。30〜34 行目では、orders を1回引いた直後に order_items への SELECT が where i1_0.order_id=? のまま5行続きます。これが N+1 です。一覧を1回引いたあとで明細を1件ずつ取りに行くため、件数 N に比例してクエリが N+1 本に膨らみます。

本編ではコードを直しません。最近この画面が重い、という曖昧な報告から、ログだけで原因を切り分けて対策の効きどころまで言える状態が到達点です。N+1 の解消は応答時間に効き、全件取得をやめる方はメモリに効きます。この2つを混ぜずに分けて言えると、後の改善でどちらから手を付けるかを決めやすくなります。OrderServiceImpl を書き換えるのは発展の D3-2+ だけです。

ログには spring.jpa.open-in-view is enabled by default の警告と、GC pause が Young Gen 450ms から Old Gen 2300ms へ伸びてヒープ使用率が 78 パーセントから 96 パーセントへ上がる様子も残っています。ここまで拾えると、遅いという報告が枯渇の手前だったことまで説明できます。メモは exercises/day3/analysis-slow-log.md に置いてください。行番号を書いておくと、後半の改善で見返すときにログを探し直さずに済みます。

休憩

[10min] ここで一度手を止めます。詰まっている方はこの間に声をかけてください。

D3-3壊れる前に気づく仕組みを足す[30min]
ねらい

何を記録すれば異常に気づけるかを自分で決めてから、その仕組みを作らせます。

この演習で扱うもの

D3-2 で見たログには、遅くなっていく様子が記録されていました。あれは記録があったから後から追えたわけです。記録がなければ、「なんか遅い」で止まって原因にたどり着けません。

いまの受注管理システムには、1回の処理にどれだけ時間がかかったかの記録がありません。また、システムが生きているかどうかを外から確かめる口もありません。

この2つを足します。処理時間の記録と、稼働状態を返す窓口です。どちらも本番で最初に入れるものです。

なぜこれをやるのか

AI にコードを書かせる量が増えると、動いているかどうかを人が全部見るのは無理になります。だから機械に見張らせます。テストは出す前に確かめるもので、今日やるのは出したあとに確かめるものです。

何を記録するかは、人が決めます。全部記録すれば安心かというと逆で、多すぎると異常が埋もれます。少なすぎると原因にたどり着けません。この線引きは業務を知っている人にしか引けません。

ここまで出たら完了
  • 何を記録するか3つ決めて、その理由を言える
  • curl -s http://localhost:8080/api/health | jq . が JSON を返し、件数が 10 になる
  • 1回のリクエストのログ行に同じ ID が入り、終了行に処理時間のミリ秒が出る
  • 起動しているだけでなく、データベースまで見て判断していることを説明できる
完了時。orderCount が 10 になり、1回の要求のログ2行に同じIDと処理時間が出ます
完了時。orderCount が 10 になり、1回の要求のログ2行に同じIDと処理時間が出ます
さわるファイル
書くsrc/main/java/com/example/order/config/WebConfig.javaaddInterceptors を追記して登録
新規src/main/java/com/example/order/config/RequestLoggingInterceptor.javaIDと処理時間。logback-spring.xml も新規
新規src/main/java/com/example/order/controller/HealthController.javaGET /api/health。HealthResponse も新規
まず自分で考える [5min]

何を記録すれば異常に気づけるかを決めてください。3つ考えます。

  • 1回の処理について、いつからいつまでを測れば処理時間になるか
  • 同じ時刻に複数の処理が動いていたとき、どの行が同じ処理のものか見分けるには何が要るか
  • 生きているかを返す窓口は、何を返せば「生きている」と判断できるか(起動しているだけで十分か)
AI と詰める

決めた3つを渡して作らせます。特に3つ目、何を返せば生きていると言えるかは、決めずに頼むと {"status":"UP"} だけが返る作りになります。

それだと、アプリは起動しているがデータベースが落ちている状態を見逃します。

@src/main/java/com/example/order/config/WebConfig.java
1回のリクエストについて、処理時間をログに出す仕組みを足してください。
同じリクエストのログ行には、共通の ID を付けてください。
対象は /api/** です。
(あわせて、決めた「生きている」の定義を書く)
手を動かす
1
考える
ログに出す3点、つまりリクエストIDをどこに持たせるか、処理時間をどこからどこまで測るか、MDC に入れた値をいつ消すかを手元に書き出します。あわせて返す JSON の形も決め、status、database.status、database.orderCount、memory.usagePercent の入れ子と型をここで固めます。
2
書く
claude を起動し、@WebConfig.java を付けて、リクエストIDと処理時間を出す RequestLoggingInterceptor の新規作成と /api/** への登録を依頼します。あわせて src/main/resources/logback-spring.xml を新規作成し、パターンを %d{HH:mm:ss.SSS} [%X{requestId}] %-5level %logger{36} - %msg%n にするところまで頼みます。
3
実行
プロジェクト直下(pom.xml がある階層)で mvn spring-boot:run を打ち、VSCode のターミナルを分割して2枚目から curl http://localhost:8080/api/orders を打ちます。同じリクエストのログ行の角括弧に同一のIDが並び、終了行に処理時間のミリ秒が出るのを読みます。
4
答え合わせ
決めた JSON の形のまま /api/health を返す HealthController の新規作成を依頼し、差分に @Autowired のフィールドが混じっていないかを見てから承認します。Java が増えたので Ctrl+C で止めて mvn spring-boot:run を打ち直し、curl -s http://localhost:8080/api/health | jq . で database.orderCount が 10 になるかを確かめます。
言葉にしてみる [1min]

記録を足したこと自体より、なぜその項目を選んだかが大事です。そこを言葉にします。

  • なぜ処理時間を記録するのか。記録しないと何に困るか
  • 同じ ID を付けるのは何のためか
  • 「生きている」の判定に、なぜデータベースまで見るのか
考えること
DB が落ちているとき、全体を DOWN にするか、database.status だけ DOWN にして status は UP のままにするか。
AI の出方
返す JSON の形と入れ子は毎回ぶれます。先に自分で書いた形をそのまま貼って渡すと揃います。@Autowired のフィールドインジェクションで書いてくることがあるので、そのときはコンストラクタインジェクションに直して、と一言足してください。
D3-3+1リクエストで発行された SQL の本数を数えて、10件を超えたら WARN を出します。Hibernate の Statistics を使うと依存を増やさずに済みますが、既定は無効です。配布時点の src/main/resources/application.yml には generate_statistics の指定が無く、このままだと本数が常に 0 で WARN が出ません。spring.jpa.properties.hibernate.generate_statistics: true を足し、設定ファイルなので Ctrl+C で止めて mvn spring-boot:run を打ち直します。しきい値は SQL_COUNT_THRESHOLD という定数に切り出してください。D1-4 で OrderDTO に明細を含める実装を終えていれば、受注一覧で SQL が11本になって WARN が出ます。未実装のままだと SQL は1本しか出ないので、しきい値を一時的に 1 へ下げて動作だけ確かめます。
くわしく(背景・詰まったときの対処)

このプロジェクトは Spring Boot Actuator を入れていません。アプリが生きているか、DB に繋がっているかを外から確かめる口が無い状態です。監視に GET /api/orders を使うと業務データを毎回読みに行くので、軽い専用の口を別に置きます。返す中身は status、application、version、database.status、database.orderCount、memory.used、memory.max、memory.usagePercent です。orderCount は配布データの受注件数と同じ 10 になります。

処理時間ログの方は、config パッケージの WebConfig に addInterceptors を足して登録します。HandlerInterceptor は、コントローラの処理の前後に共通処理を差し込む仕組みで、入り口の preHandle と出口の afterCompletion を使います。リクエストIDは MDC に入れます。MDC はログ出力ライブラリがスレッド単位で値を持つ置き場で、消し忘れると次のリクエストに前のIDが残ります。afterCompletion の最後に消す行が入っているかを、差分で必ず見てください。MDC に入れただけではログ行にIDは出ません。既定のログパターンに %X{requestId} が無いためで、logback-spring.xml を新しく作ってパターンを差し替えるところまでが1組です。

依存の受け取り方は2通りあります。private final のフィールドをコンストラクタで受ける形が規約で推奨、@Autowired をフィールドに付ける形が規約で禁止です。差分に @Autowired の行があれば直させてください。新しく作るのは RequestLoggingInterceptor、logback-spring.xml、HealthController、応答を組み立てる src/main/java/com/example/order/dto/HealthResponse.java の4本で、これに WebConfig への addInterceptors 追記が加わります。レスポンスは Map で組まず、record か DTO の専用型にします。JdbcTemplate の Bean が見つからないと言われたら、EntityManager のネイティブクエリで SELECT 1 を実行する形に直させます。jq が入っていない環境では、| jq . を外して curl http://localhost:8080/api/health だけで中身を読めます。細かい打鍵と模範プロンプトは exercises/day3/exercise2-監視とセキュリティ.md にあります。

この口ができたら、Day1 で作った src/main/resources/static/dashboard.html から /api/health を呼び、自分の画面の上に UP と受注件数を並べられます。この画面ファイルはアプリを起動したまま書き換えられ、保存してブラウザを再読み込みすると新しい中身に差し替わります。表示を見て気に入らないところを claude に直させ、また再読み込みして見る、という往復をそのまま続けられます。止めて起動し直すのは Java か設定ファイルを書き換えたときだけです。

新規4本と追記1本を30分で通します。時間内に両方が届かないときは、先にヘルスチェックを動かしてください。処理時間ログは logback-spring.xml までを1組として、残った時間で足します。

D3-4レビュー結果から根拠のないものを落とす[20min]
ねらい

AI が挙げた指摘を、根拠があるものだけに絞り込みます。全部直すのが目的ではありません。

この演習で扱うもの

3日かけて手を入れてきたコードを、最後に見直します。対象は業務の判断をする service、外からの入口の controller、それと設定です。

配布物には docs にチェックリストが2つ入っています。コーディング規約と、セキュリティチェックリスト。この2つが、指摘を採るか落とすかの基準になります。

実際に1つ、明らかな穴があります。外部からの接続を全部許可する設定です。開発中は便利ですが、そのまま本番に出ると誰でも叩ける状態になります。

なぜこれをやるのか

AI にレビューさせると、指摘は必ず返ってきます。10件でも20件でも出ます。そのうち本当に直すべきものは、たいてい2〜3件です。

全部直そうとすると時間が溶けます。逆に全部無視すると意味がありません。だから絞り込みが要ります。基準は「根拠を示せるか」です。どのファイルの何行が問題かを言えない指摘は、一般論であって、このコードの話ではありません。

この絞り込みは、現場に持ち帰ってそのまま使えます。AI のレビューを鵜呑みにしないための、いちばん簡単な線引きです。

ここまで出たら完了
  • 自分で挙げた3点のうち、AI も指摘したものと、しなかったものを言える
  • 指摘が重要度で分かれ、それぞれにファイル名と行番号が付いている
  • 行番号を示せなかった指摘を1つ以上落とし、落とした理由を言える
  • 外部からの接続を絞ったあと、別のドメインからの要求が 403 で弾かれる
完了時。別ドメインからが 403 で弾かれ、素の要求は 200 のままです
完了時。別ドメインからが 403 で弾かれ、素の要求は 200 のままです
さわるファイル
読むdocs/セキュリティチェックリスト.md指摘を突き合わせる基準
読むdocs/コーディング規約.md禁止パターンの基準
書くsrc/main/java/com/example/order/config/WebConfig.javaCORS の全許可を絞る
まず自分で考える [5min]

AI に見せる前に、自分で気になる点を3つ挙げてください。細かい書き方ではなく、動かしたときに困りそうなところで探します。

  • エラーが起きたとき、外にどういう形で返っているか(Day1 で 500 が返ったのを思い出す)
  • 外部から誰でも叩ける状態になっていないか
  • ログや例外メッセージに、外に出したくない情報が混ざっていないか
AI と詰める

自分の3点を添えてレビューさせます。返ってきたら、1件ずつチェックリストの項目に紐づけてください。紐づかないものには「どのファイルの何行ですか」と聞き返します。

示せなければ落とします。落とした指摘の数も成果です。

@src/main/java/com/example/order/service/OrderServiceImpl.java
@src/main/java/com/example/order/controller/OrderController.java
@src/main/java/com/example/order/config/WebConfig.java
@docs/セキュリティチェックリスト.md
セキュリティと規約の観点でレビューしてください。
指摘には必ずファイル名と行番号を付けてください。
重要度を Critical / Warning / Info に分けてください。
手を動かす
1
考える
OrderServiceImpl と OrderController をざっと読み、気になった点を3つメモします。例外ハンドラが無いこと、changeStatus が Map で入力を受けていることあたりが候補です。
2
書く
claude を起動し、@OrderServiceImpl.java と @OrderController.java と @WebConfig.java を付けて、セキュリティ・規約・テスト容易性の3観点で Critical と Warning と Info に分けた指摘を依頼します。添付コードに実在する箇所だけ、と添えます。
3
答え合わせ
返ってきた指摘を2つのチェックリストの項目へ1件ずつ紐づけます。紐づかないものには、どのファイルの何行か、と聞き返し、示せなければ落とします。
4
実行
@WebConfig.java を付けて allowedOrigins を http://localhost:8080 だけに絞る形に直して と依頼し、差分を読んで承認します。Java を変えたので Ctrl+C で止めてから mvn spring-boot:run を打ち直し、curl -i -H "Origin: http://example.com" http://localhost:8080/api/orders が 403 と Invalid CORS request を返すのを見ます。
言葉にしてみる [1min]

落とした指摘のほうを説明してみてください。採るより落とすほうが判断が要ります。

  • AI の指摘のうち、落としたものは何か。なぜ落としたか
  • 残したものは、なぜ残す価値があると判断したか
  • この絞り込みを、自分の現場でどう使うか
考えること
Claude Code が挙げた指摘のうち、チェックリストに対応する項目が無いものをどう扱うか。
AI の出方
実在しない問題を Critical で挙げることもあれば、CORS の全許可を素通りさせることもあります。指摘ごとに、どのファイルの何行か、と聞き返して、コードに無ければ落としてください。
D3-4+CLAUDE.md を CLAUDE.md.off に改名します。CLAUDE.md は claude の起動時に読み込まれるため、改名しただけでは起動中のセッションには効きません。claude をいったん終了して起動し直してから同じ依頼をもう一度投げ、ステータス定数の使われ方や @Transactional の付き方がどう変わるかを見ます。確認したら名前を戻し、もう一度起動し直します。
くわしく(背景・詰まったときの対処)

レビューの基準は docs/セキュリティチェックリスト.md と docs/コーディング規約.md の2つです。前者は SQL のパラメータ化、入力値の検証、個人情報をログや例外メッセージに出さない、例外の詳細を API レスポンスに含めない、DTO と Entity の分離、H2 コンソールの本番無効化、機密情報をログ・設定に残さない、CORS を必要最小限に、という並びです。後者には禁止パターンとして、フィールドインジェクション、生 SQL の文字列連結、空 catch、System.out.println が挙がっています。手順の細かい打鍵と模範プロンプトは exercises/day3/exercise2-監視とセキュリティ.md にあります。

CustomerController の findAll は Customer エンティティをそのまま返しています。email と phone と address が丸ごとレスポンスに出る形です。createCustomer は @RequestBody Customer でエンティティを直接受けるので、利用者に決めさせたくない項目まで外から書き換えられます。WebConfig の addCorsMappings は allowedOrigins が全許可。3つとも配布時点の実コードにあり、探せば見つかります。

/review は直近で変更したファイルを規約に照らして見るコマンドです。観点を細かく指定したいときは、@ でファイルを渡して自然言語で頼む方が狙いどおりになります。指摘一覧を残すなら exercises/day3/review-result.md あたりです。Origin を付けない素の curl は絞る前も後も 200 を返すので、それは設定を壊していない確認として読みます。絞る前に Origin: http://example.com を付けると 200 と Access-Control-Allow-Origin: * が返り、絞ったあとは Spring が同じ要求を 403 と本文 Invalid CORS request で弾きます。ヘッダだけ消えた 200 にはなりません。直す1件は CORS が手軽ですが、例外ハンドラを新規に作る方を選んでもかまいません。ちなみに例外ハンドラを入れると、存在しない ID への問い合わせが 500 から 404 に変わるので、直した効果がそのまま画面に出ます。

講師の実演 公式プラグインを入れて動かす

[10min] 発展枠の最後に、講師機で2つ動かします。手元の操作は要りません。時間が押していれば飛ばします。

推奨をそのまま入れず、何が変わるかを1つずつ確かめてから採ります。導入の手順は claude で /plugin を開いて claude-code-setup と skill-creator を選ぶ形です。受講者の環境には入れません。

到達チェック

この日の終わりに、自分で確かめてください。全部埋まっていなくても構いません。

つまずいたとき

よく出る詰まりどころです。当てはまるものがなければ、その場で声をかけてください。

手は動いたけれど、理解できている自信がありません
各演習の最後に「言葉にしてみる」を置きました。1分だけです。声に出して詰まったところが、まだ入っていないところなので、そこを質問してください。逆に言えば、言えたところは理解できています。3日間ずっと手は動いていたので、心配されているほど入っていないわけではありません。自信が持てないのは、確認する機会が無かったからです。
ペースについていくので精一杯で、中身が入っていません
今日は演習を2本ぶん短くして、そのぶん言葉にする時間と振り返りに回しました。それでも速いと感じたら、チャットに「まだです」とだけ書いてください。内容の説明は要りません。人数だけ分かれば待ちます。研修中に全部を理解しきる必要もありません。教材ページと配布データは手元に残るので、あとから同じ手順をなぞれます。
3日間やってきたことが、業務のどこで使えるのか整理できません
最後の20分で、3日間を1枚に並べます。書く(Day1)、確かめる(Day2)、直す(Day3)の3つで、どれも現場でそのまま使える形にしてあります。そこで「明日ひとりでできること」と「今週チームでできること」に分けるので、持ち帰る形はそこで決まります。感想ではなく作業として書き出してください。
Java が読めません。原因の切り分けができますか
できます。今日見るのはコードではなくエラーとログです。同じ行が何百回も繰り返されていたら、そこが原因です。時間の数字が急に大きくなっていたら、その手前が原因です。この読み方は COBOL でも Python でも C# でも同じで、Java の知識は要りません。切り分けたあとの修正は Claude Code にやらせます。
エラーが大量に出て、どこを見ればいいか分かりません
全部読まなくて構いません。上から5行と、繰り返されている行の名前だけ見てください。それを Claude Code にそのまま貼って「これが出ています」と伝えるのが最短です。読んでから貼る必要はありません。貼ってから一緒に読みます。
この進め方を自分の現場に持ち帰れますか
持ち帰れます。今日の4本は、止まった・遅くなった・様子が見えない・読んでほしい、の4場面です。どれも言語や業務を選びません。使う型は Day2 と同じで、対象・やること・やらないこと・終わりの合図の4つです。
生成されたコードが正しいのか、自分で判断できる自信がありません
判断の基準を自分の外に置いてください。D3-1 は最終行の集計、D3-3 は curl が返す JSON、D3-4 は docs の2つのチェックリストが基準です。読んで意味が取れない行が出たら、その行だけを指して、ここが何をしているか1行で説明して、と claude に聞き返してください。
Planモードに切り替えるのを忘れます
入力欄で Shift+Tab を押すたびにモードが切り替わります。表示が plan mode になるまで押してください。忘れて実装が始まっても、ファイルを書き換える前に許可を聞かれるので、そこで断れば止まります。複数ファイルに広がる依頼や新規クラスの生成の前だけ意識すれば十分で、1行の修正まで計画を挟む必要はありません。
javac は通るのに ClassNotFoundException で起動できません
-d . を付け忘れると、クラスファイルがソースと同じ場所に出て、パッケージ階層をたどる起動指定とずれます。javac -encoding UTF-8 -d . CrashingOrderProcessor.java からやり直し、java -Dfile.encoding=UTF-8 -cp . com.example.order.debug.CrashingOrderProcessor で起動してください。
curl を打っても結果が出ません
アプリが起動しているか、URL が合っているかを見てください。整形して読むための | jq . は、jq が入っていない環境では外して curl http://localhost:8080/api/health だけでも中身を読めます。
ログ行にリクエストIDが出ません
MDC に入れただけでは出ません。既定のログパターンに %X{requestId} が無いためで、src/main/resources/logback-spring.xml を新しく作り、パターンを %d{HH:mm:ss.SSS} [%X{requestId}] %-5level %logger{36} - %msg%n にしてから起動し直してください。
/api/health が 404 になります
Java のクラスは起動し直すまで反映されません。起動中のターミナルで Ctrl+C を押し、mvn spring-boot:run を打ち直してください。それでも 404 なら、クラスの @RequestMapping とメソッドの @GetMapping を合わせたパスが /api/health になっているかを差分で確認してください。
dashboard.html を直しても表示が変わりません
アプリは止めなくて構いません。src/main/resources/static/ に置いた HTML・CSS・JavaScript は保存した時点で配信対象に入るので、まずブラウザを Ctrl+F5 で読み込み直してください。それでも古いままなら、保存先が src/main/resources/static/ になっているか、開いている URL が http://localhost:8080/dashboard.html かを見てください。