1. そのタイミング · アプリは外から動くのに内側がきしむとき
最初はすべてが魔法だ。AIに「プロフィール画面を追加して」と言えば現れる。「次にエクスポート用のボタン」と言えば現れる。速い、とても速い。でもあるタイミング — ほとんどいつも3週目から10週目のあいだ — で何かが変わる。小さな機能を頼んでももう瞬時ではない。AIは時間がかかり、混乱し、触るべきでないところを触る。触ってもいない場所にバグが出る。アプリは動く、でも動かすのが重くなった。靴に泥がついたまま歩くように。
それがEXアクトなタイミングだ。劇的なことは起きていない。目に見えるものは何も壊れていない。起きたのは目に見えないもので、それは技術的負債と呼ばれる。AIは高速に構築し、高速に構築する者みなと同じく、瓦礫を残した。もう誰も使わないのにそこにあるコード、5つのファイルにコピーされた同じロジック、すべてのアラームを消すany型、いい加減に組まれたアニメーション。それらは画面には映らない。それらすべてが、これからのどんな変更も難しくする。
2. その痛み · どこから来るのか、そしてなぜAIは(「ちゃんと」書いても)汚すのか
ここが大事で正直なところ。AIは悪いコードを書くのではない。動くコードを書く。問題は、「今動くこと」を最適化していて、「3ヶ月後に保守しやすいこと」ではない点だ。この2つは違う方向に引っ張り合う。だから、一つ一つは良くても、その総和は汚れる。これがAIが残す4種類の瓦礫と、なぜ残すのかだ。
- 死んだコード。 変更を頼むと、AIは新しい関数を書き直す…でも古いのを消し忘れる。それが孤児のまま、誰にも呼ばれずそこに残る。これを何百回もの変更で掛け算すれば、もう何の役にも立たないファイルが丸ごとできあがる。
- 重複。 AIはその同じロジックを前に書いたことをいつも覚えているわけではないので、また書く。今や同じルールが5箇所に住んでいる。ルールが変わる日には5回直さなければならず — 必ず1つ忘れる。
- `any`とゆるい型。 AIがあるデータの型に確信が持てないとき、近道をとる。それを
anyとマークする。それで、TypeScriptがあなたにエラーを知らせるために持っているすべてのアラームを消す。コードはコンパイルする…そして本番で爆発する。 - 雑に組まれたアニメーションとエフェクト。 AIはアニメーション付きグラデーション、ブラー、シャドウを置く — 見た目は良いが、雑に組まれるとGPUに汗をかかせ、非力なスマホやノートPCではアプリが遅く感じる。
3. 外科手術の7フェーズ(すべての核心)
外科医は開けてでたらめに探ったりしない。プロトコルに従う。順番に、一歩ずつ。コードの外科手術も同じだ。7つのフェーズがあり、順番が重要だ。まず死んだものを取り(消すつもりのコードを掃除しないため)、次に重複を融合し、次にアニメーションを堅牢化し、次にReactのパフォーマンス、次に型、次にGPUを鎮め、最後に健康レポートを出す。それぞれをここに。
フェーズ1 · 死んだコードを除去する
最初にすべきは、もう息をしていないものすべてを手術台から取り除くこと。これは最も割の良いフェーズだ。他のすべてのために地面をきれいにするから。消すつもりの関数を最適化しても意味がない。
- 孤児ファイル — どこからも誰もインポートしないファイル。消されなかった古いバージョンから、そこにある。
- 消費者のいないexport — 誰も使わないエクスポートされた関数やコンポーネント。「念のため」でエクスポートされ、その念のためは決して来なかった。
- 使われていない変数・関数・import — 宣言されて忘れられたもの。リンターがよくマークするが、AIは「邪魔にならない」と残す。邪魔になる。ノイズだ。
- 到達不能なコードの分岐 — 決して真になれない条件、
returnのあとのコード、もう誰も発火しないif。
フェーズ2 · 重複を監査する
地面がきれいになったら、次に繰り返されているものを探す。職人のルールはシンプルだ。何かが3回以上現れたら、それは偶然でなくなり、住む場所が1つあるべきパターンになる。
- 繰り返されたロジック → 両方の場所が呼ぶヘルパー関数(
helper)に抜き出す。1つのルール、1つの場所。 - 繰り返されたマジックナンバーとマジック文字列(同じ
48、同じURL、同じ色) → 名前付き定数に集約する。一度変えれば、全箇所で変わる。 - 3回以上繰り返されたJSX(同じカード、わずかに違うバリエーションの同じボタン) → その違いをpropsで受け取るサブコンポーネントに変える。
- `useState`+そのhandlerの繰り返しパターン(同じ状態と更新関数が複数コンポーネントにコピーされている) → 自前のフック(
custom hook)に抜き出し、ロジックを一度だけカプセル化する。
フェーズ3 · アニメーションのルールを守る
アニメーションはAIが最も即興し、最も微妙な欠陥を残すところだ — 壊れはしないが、アプリを「安っぽく」感じさせるもの。チラつき、ジャンプ、始動しないトランジション。これが外科手術が検証する具体的なルールだ(ReactのアニメーションライブラリであるMotion / Framer Motion — エコシステムの標準 — に適用される)。
- `transparent`へ/からのアニメーションはチラつく可能性がある → 代わりに
rgba(0,0,0,0)を使う。transparentという語を色と補間すると、途中で醜いチラつきが出ることがある。rgba(0,0,0,0)はきれいに補間する。 - `transition`の重複なし — 互いに争う2つのトランジション定義を持つアニメーションは予測不能な結果になる。タイミングの真実の源は1つだけ。
- `border`ショートハンド(太さ・スタイル・色をまとめる短縮記法)をアニメーションしない →
borderColorのように個別のプロパティをアニメーションする。ショートハンドはきれいに補間しない。 - アニメーションで`background` → `backgroundColor` —
backgroundショートハンド全体をアニメーションするのは問題を起こす。変わる特定のプロパティだけをアニメーションする。 - 入退場するリスト(`AnimatePresence`)で安定したkey — アニメーションするリストの要素が安定した一意の
keyを持たないと、退場アニメーションが壊れ、要素がジャンプする。keyは本物のidであるべきで、配列のインデックスであってはならない。
フェーズ4 · Reactのパフォーマンス
ここで外科手術は、Reactが不必要に繰り返す仕事を探す。不要な再レンダー1つ1つが少しの遅さで、積み重なるとアプリが重く感じる。具体的な4つのことを見直す。
- `useCallback`なしでpropsとして渡されるhandler → ラップする。そうしないと、毎レンダーごとに新しい関数が作られ、何も変わっていなくても子コンポーネントを再レンダーさせる。
- `useMemo`なしの高価な計算(大きなリストのフィルタ/ソート、重い計算) → メモ化する。そうしないと、データが同一でも毎レンダーで再計算される。
- 依存が正しく宣言されていない`useEffect` — 依存が多すぎると理由なくエフェクトが発火し、少なすぎると古いデータを使う。どちらもバグだ。依存配列はエフェクトが使うものを正確に列挙すべき。
- 安定した`key`のない`.map` — 一意のkey(またはインデックス使用)なしでリストをレンダーすると、並べ替えでReactが混乱して状態を失う。各要素は自分のidが必要。
useCallbackとuseMemoでラップすること。ダメだ。メモ化にもコストがある(メモリ、複雑さ)。外科手術は、本当に測定可能なメリットがあるところだけメモ化する — 多くの子に降りるhandler、本当に高価な計算。やりすぎの最適化は、それ自体が汚れの一形態だ。フェーズ5 · TypeScriptの衛生
TypeScriptはあなたのコードのアラームシステムだ。エラーがユーザーに届く前に知らせてくれる。でも、型を見せた場合にだけ機能する。anyの一つ一つは、誰かが消したアラームだ。このフェーズがそれを再点灯する。
- `any` → 本物の型、または`unknown` —
anyはすべてのチェックを無効にする。本当に型が分からないなら、使う前に検証を強制するunknown(安全)を使う。何も強制しないany(危険)ではなく。 - `as 型`のキャストを見直す — TypeScriptに「信じて、これはこの型だ」と言うのは、嘘かもしれない約束だ。
asの一つ一つは、コンパイラがチェックをやめた地点。1つずつ見直す。本当か、それとも継ぎ接ぎか? - 一度しか使われないexportを内部化する — 型や関数がエクスポートされているのに、自分のファイル内でしか使われていないなら、公開すべきでない。privateに下げる。表面が減り、ノイズが減る。
- コンポーネントのpropsに型をつける — 各コンポーネントは、どのpropsをどの型で受け取るかを宣言すべき。型なしのpropsは変装した
anyだ。
フェーズ6 · GPUの崩壊を監査する
これはほとんど誰も知らないフェーズで、非力なデバイスで最も目立つものだ。ある種の視覚エフェクトは美しいが、描くのが残酷なほど高価だ。乱用すると、グラフィックカードが飽和してアプリがカクつく。外科手術は古典的な3人の犯人を探す。
- アニメーション付きの円錐グラデーション(`conic-gradient`) → CSSの
@keyframesによるアニメーションに移す。円錐グラデーションを毎フレームJavaScriptでアニメーションするのは非常に高価だ。keyframesならブラウザが最適化する。 - 多くの`repeat: Infinity`(同時に複数の無限ループアニメーション) → 統合する。10個の永遠のアニメーションが並行して走ると、GPUを起こし続けバッテリーを焼き続ける。まとめるか、本当に必要なものだけに減らす。
- 繰り返される多くの小要素に`backdrop-blur` → 同じ背景ブラーが20個の小さなカードで繰り返されると、GPUはそれを20回再計算する。小さく繰り返す要素には、単色(または半透明)の背景がほぼ同じに見え、コストは何分の一かで済む。
backdrop-blur、あの流行りのすりガラス効果)は、GPUに要素の後ろにあるすべてを見てリアルタイムでぼかすことを強いる。1つだけ、大きなものなら何ともない。20個の小さなものが繰り返されるのは、カードに1フレームあたり20回画面をぼかせと頼むようなものだ。そこがあなたのユーザーのノートPCがファンを回し始めるところだ。フェーズ7 · 健康レポート
すべての外科手術は報告書で終わる。レポートがなければ、何を触ったか分からず、巻き添えの損傷がなかったと信じることもできない。最後のフェーズは、エージェントがやったことすべてを、あなたの言語で、明確な要約として渡すことだ。
- フェーズごとの件数 — 孤児ファイル何個、融合した重複何個、除去した
any何個、修正したアニメーション何個、など。 - 除去した行数 — 出て行った死んだコードの総行数。最も満足感のある数字だ。もう保守しなくていいコード。
- 「本番投入可能」の判定 — 視覚も挙動も何も変えず、内部構造だけを変えたという明示的な確認。
- 触らなかったものとその理由 — 候補に見えたが、わざと残したもの(違う理由で変わる重複、割に合わないメモ化)。何をしないと決めたかの正直さ。
4. その習慣 · 外科手術は一度きりの手術ではなくメンテナンスだ
最もよくある間違いは、外科手術をすべてがめちゃくちゃになったときに一度だけやるものと考えることだ。違う。AIのコードは常に汚れる。構築セッションのたびに新しい瓦礫を残すからだ。外科手術は緊急手術ではない。根管治療にたどり着かないために半年ごとにやる歯のクリーニングだ。
- 大きな構築スプリントのあと — AIがちょうど大量のコードを一気に生成したとき、それが新鮮な瓦礫だ。その上に積もる前に掃除する。
- 重要な機能を始める前 — AIに構築するためのクリーンなキャンバスを渡す。汚れたコードの上には汚く構築される。
- AIが混乱し始めたと気づいたとき — ツールが触るべきでないものを触ったり迷ったりしたら、ノイズが多すぎるサインだ。外科手術が明晰さを返す。
- プロジェクト全体を一気にではなく、1ファイル・1モジュールずつ — 1つのモジュールを手術するのは安全でレビュー可能だ。全部を一気にやるのは麻酔なしの開胸手術だ。少しずつ行く。
5. マスタープロンプト · エージェントに渡してフェーズごとに手術させる
ここが要のピースだ。あなたのコードエージェントを、視覚を何も触らずに7フェーズを順番に1ファイルまたはモジュールに適用する外科医に変える、たった1つのプロンプト。[角括弧]を埋めて、貼り付けて、手術させる。その前に一言。まずGitHubでコミットを(あなたの安全網)、そして全プロジェクトではなく1つのモジュールに向ける。
デバッグの熟練コード外科医として振る舞え。このファイル/モジュールを手術する。[ファイルまたはフォルダのパス、例: src/components/Dashboard/]。スタックは: [例: React + TypeScript + Motion/Framer Motion + Tailwind]。 神聖かつ不可侵のルール: 視覚的な変更ゼロ、挙動の変更ゼロ。アプリは前と後でEXアクトに同じ見た目、同じ挙動でなければならない。内側だけを掃除し堅牢化する。もっと掃除するか挙動を保つかで迷ったら、常に挙動を保つほうが勝つ。使われていないと確信できないものは何も消すな。マークして私に聞け。 この7フェーズを順番に適用せよ。前のフェーズを終えずに次へ進むな。各フェーズの最後に、見つけたものと変えたものを1行で伝えよ。 フェーズ1 — 死んだコード: 孤児ファイル(誰もインポートしない)、消費者のいないexport、使われていない変数/関数/import、到達不能なコード分岐を除去せよ。疑わしいものを消す前に、リストにして私の確認を待て。 フェーズ2 — 重複: 繰り返されたロジック(→ヘルパーに抜き出す)、繰り返されたマジックナンバー/文字列(→名前付き定数)、3回以上繰り返されたJSX(→propsを持つサブコンポーネント)、繰り返されたuseState+handlerパターン(→custom hook)を探せ。似ているだけで違う理由で変わるものは融合するな。 フェーズ3 — アニメーションのルール: transparent→rgba(0,0,0,0)を修正し、重複したtransitionを除去し、borderショートハンドをアニメーションせず(borderColorを使う)、アニメーションでbackground→backgroundColorに変え、AnimatePresenceのリストで安定した一意のkey(決してインデックスではなく)を保証せよ。 フェーズ4 — Reactパフォーマンス: propsとして渡されるhandlerをuseCallbackでラップし、高価な計算をuseMemoでメモ化し、useEffectの依存配列を修正し(多すぎず少なすぎず)、各.mapに安定したkeyを追加せよ。念のためのメモ化はするな。本当にメリットがあるところだけ。 フェーズ5 — TYPESCRIPT: 各anyを本物の型かunknownに置き換え、各'as 型'キャストを見直し(本当か、継ぎ接ぎか?)、自分のファイル内でしか使われないexportを内部化し、すべてのコンポーネントのpropsに型をつけよ。 フェーズ6 — GPUの崩壊: アニメーション付き円錐グラデーションをCSS @keyframesに移し、repeat:Infinityが同時に多くあれば統合し、何度も繰り返される小要素のbackdrop-blurを単色/半透明の背景に置き換えよ。 フェーズ7 — 健康レポート: 終わったら、フェーズごとの件数、除去した総行数、視覚も挙動も何も変えなかったという明示的な確認、そして触らないと決めたものとその理由のリストを含むレポートを渡せ。 フェーズごとに作業し、各フェーズのdiffを見せ、神聖なルールを危険にさらすものがあれば、止まって進む前に私に聞け。
6. 外科手術が傷を残さなかったか検証する
外科手術は閉じたときに終わるのではない。患者が無事だと確認したときに終わる。神聖なルール(挙動の変更ゼロ)は、信じるだけでなく確認しなければならない。数秒でそれを確認してくれる自動の安全網が2つあり、あなたが何もプログラミングせずにエージェントが走らせられる。
# 1) 型チェック: エラーゼロ = 外科手術が契約を壊さなかった npx tsc --noEmit # 2) テスト: 前に通っていて後も同じように通れば、 # 挙動は保たれた npm test
git diffでdiff(変更)を見よ。読み方のルール。見えるもののほぼすべては、赤い行(削除)か場所の移動であるべきで、新しいロジックではない。 ユーザーが見る文字列が変わっていたり、デフォルト値が違ったり、新しい条件が現れたら、そこに傷の可能性がある — 受け入れる前にAIに理由を聞け。7. 最も簡単な道 · AIがチャットで何をやり、あなたが何を決めるか
複雑にならないように。外科手術の大部分は、あなたのコードエージェントがチャットで自分でやる。あなたの仕事は指揮と承認だ。これが分担だ。
- モジュールを巡回し、死んだコード、重複、
any、雑に組まれたアニメーションを検出する — あなたはマスタープロンプトを渡すだけ。 - フェーズごとに修正を適用し、各フェーズのdiffを見せる。
tscとテストを走らせ、何も壊さなかったことを確認する。- 件数と、触らないと決めたものを含む最終健康レポートを書く。
- 先に安全のためのコミットをする — あなたの元に戻すボタン。それは手術前に、GitHub Desktopかチャットからあなたがやる。
- 疑わしい削除を承認する — AIが何かが使われているか確信が持てないとき、あなたに聞く。最終決定はあなたのもの。
- diffを見直す — 変更を読み、受け入れる前に挙動の傷がないと確認する。
- 範囲を決める — どのモジュールをどの順番で手術するか。「全部手術して」と委任するな。少しずつ行く。