Claude Media
Claude Codeリファクタリングを安全に進める手順 — テストが無いコードの扱い方

Claude Codeリファクタリングを安全に進める手順 — テストが無いコードの扱い方

既存コードの改修でClaude Codeが暴走するかどうかは、読ませる範囲・振る舞いの固定・一度に触る量で決まります。テストが無い場合の始め方から手順にしました。

Claude Codeでのリファクタリングが荒れるとき、原因は変更の中身より手前にあります。読ませる範囲が広すぎる、振る舞いを固定するものが何も無い、一度の指示で触る範囲が大きすぎる。この3つのどれかです。ここではテストが揃っていない既存コードを前提に、着手前に何を用意し、どの順番で何を渡すと改修が安全に進むかを手順としてまとめます。

Claude Codeのリファクタリングは安全網づくりが本体

Claude Codeでのリファクタリングとは、外から見える振る舞いを変えずに内部構造だけを作り替える作業を、合否を返す検証手段を先に用意したうえでClaudeに任せる進め方です。作業そのものは得意な部類に入ります。壊れるのは検証の側です。

理由は単純です。Claudeは作業が終わったように見えた時点で止まります。自分で走らせて結果を読める検証手段が無ければ、「終わったように見えるか」が唯一の判断材料になり、間違いは人間が気づくまで待ちます。新規実装ならこれでも致命傷になりにくい。まだ誰も使っていないからです。改修は違います。動いていたものが静かに動かなくなり、しかも差分は「きれいになった」ように見えます。

だから検証手段を先に用意する順番が、そのまま改修の安全度になります。テストを先に書く流れは新規実装のテスト駆動開発と同じですが、狙いは別物です。新規実装のテストは「これから作る仕様」を書きます。改修のテストは「いま動いている挙動」を書きます。テスト先行の型そのものはClaude Codeでテスト駆動開発を回す手順で扱っているので、ここでは改修に固有の部分だけを追います。

改修に入る前に揃えるもの

必要なものは3つだけです。git上できれいな作業ツリー、対象の振る舞いを確認できるコマンド1つ、そしてビルドとテストの手順が書かれたCLAUDE.md です。

作業ツリーをきれいにしておく理由は、セッション内の巻き戻し機能がgitの代わりにならないためです。Claude Codeは送信したプロンプトごとにチェックポイントを作り、/rewind またはEscの2回押しでコードと会話をその時点へ戻せます。ただし戻せる範囲には限界があります。

CLAUDE.md は毎回のセッションで読み込まれるので、テストコマンドとビルドコマンドはここに書きます。まだ無ければ /init が既存の構成を読んで下書きを作ります。読み込まれたかどうかは /context で確認できます。

/init
/context

書く量は絞ります。1行ごとに「これを消したらClaudeは間違えるか」を問い、答えがノーなら消す。長い CLAUDE.md は肝心の指示が埋もれて無視されます。改修方針のように「その作業のときだけ必要な知識」は、CLAUDE.md ではなくスキルに置くと毎回のセッションを太らせずに済みます。

手順1: 読ませる範囲を先に絞る

改修対象が大きいほど、最初にやることは「読ませない範囲」の指定になります。Claudeのファイル検索は .gitignore に従うので、node_modules/dist/ のように既に無視されているパスは何もしなくても検索結果に出てきません。問題はリポジトリに入っているベンダーコードや生成物です。

こちらは permissions.denyRead ルールで塞ぎます。

.claude/settings.json での読み込み拒否ルール
{
  "permissions": {
    "deny": [
      "Read(./**/dist/**)",
      "Read(./**/build/**)",
      "Read(./**/*.generated.*)",
      "Read(./vendor/**)"
    ]
  }
}

リポジトリ全員に効かせるなら .claude/settings.json にコミットし、自分だけなら .claude/settings.local.json に書きます。相対パターンはClaude Codeを起動したディレクトリを基準に解決されます。サブディレクトリから起動することがあるなら、Read(//absolute/path/to/repo/vendor/**) の形で絶対パスにしてください。

このルールはClaude組み込みのファイルツールに加えて、cat head grep find のように認識されたBashコマンドにも効きます。ただし再帰検索の出力から拒否パスを取り除くわけではなく、自分でファイルを開く任意のサブプロセスにも効きません。読み込み量を減らす仕掛けであって、機密の隔離手段として設計されたものではない点は押さえておきます。

定義や参照をたどる作業は、ファイルを片端から読ませるより言語サーバーに任せたほうが安く済みます。公式マーケットプレイスにはTypeScript・Python・Go・Rustなどのコードインテリジェンスプラグインがあり、セッション内から入れられます。

/plugin install typescript-lsp@claude-plugins-official

利用には各開発者のマシンに対象言語の言語サーバーバイナリが入っている必要があります。

改修が別パッケージにまたがるときは、起動ディレクトリの外にアクセス権を渡します。渡し方は2通りあり、読み込まれるものが違います。

渡し方CLAUDE.mdとルールスキル
additionalDirectories 設定CLAUDE.mdとルール読み込まれないスキル読み込まれない
--add-dir フラグ / /add-dirCLAUDE.mdとルール環境変数を立てたときだけスキル読み込まれる

--add-dir で追加したディレクトリの CLAUDE.md まで読ませたいときは CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 を付けて起動します。この環境変数は additionalDirectories 設定側には効きません。

そして調査そのものはサブエージェントに出します。組み込みのExploreは読み取り専用でWriteとEditが拒否されており、探索の結果だけがメインの会話に返ります。ExploreはCLAUDE.md と親セッションのgitステータスを読み込まない設計なので、プロジェクト固有の約束事を前提にした調査を頼むときは、その前提をプロンプトに書き添えます。範囲を切らずに「調べて」と投げると数百ファイルを読んでコンテキストが埋まります。モノレポでの起動位置や除外設計はClaude Codeモノレポ設計で詳しく扱っています。

手順2: いまの振る舞いを固定するテストを先に作る

テストが1本も無いコードを改修するとき、最初に書くのは仕様を書いたテストではありません。現状の出力をそのまま記録するテストです。古くはcharacterization test(振る舞い記録テスト)と呼ばれてきた型で、目的は正しさの証明ではなく「改修の前後で同じか」を機械に判定させることにあります。

この区別を飛ばすと、改修とバグ修正が同じ差分に混ざります。既存の挙動が仕様として妙でも、いったんそのまま固定する。おかしな点は改修が終わってから別の変更として直します。Claudeに渡すときは、この意図を明示したほうが早いです。

src/billing/invoice.ts の calculateTax を改修する前に、
いまの振る舞いを固定するテストを書いてください。
仕様として正しいかは判断せず、現在の実装が返す値をそのまま期待値にします。
境界値と、税率が0のケースを含めてください。モックは使わないでください。

Claudeは既存のテストファイルを見て、使われているフレームワークとアサーションの書き方に合わせます。こちらから指示する値打ちがあるのは、対象・観点・避けてほしい手法の3つです。「どのファイルの」「どの状況を」「モックあり/なし」を書くだけで、書き直しの往復が減ります。

検証手段はテストである必要すらありません。ビルドの終了コード、リンター、出力をフィクスチャと突き合わせるスクリプト、画面のスクリーンショット比較。会話のなかでClaudeが読み取れる合否を返すものなら役割は同じです。テストの器すら無いリポジトリなら、まずは終了コードが返るシェルスクリプト1本から始めても成立します。

そして全体を覆おうとしないことです。改修で触る境界の内側と外側、そこだけ押さえれば足ります。カバー率を上げる作業は、それ自体が別のプロジェクトになります。

手順3: plan modeで触る範囲を確定させる

安全網ができたら、実装に入る前に計画を先に出させます。plan modeはClaudeに調査と提案だけをさせるモードで、ファイルを読みシェルコマンドで探索はしますが、ソースは編集しません。計画を承認するまで編集はブロックされたままです。

入り方は3つあります。セッション中はShift+Tab でモードを循環させる、単発なら /plan をプロンプトの先頭に付ける、起動時から入るならフラグを渡します。

claude --permission-mode plan

改修で効くのは、出てきた計画を Ctrl+G でエディタに開いて直接手を入れられる点です。ここで書き足す価値が高いのは、やることではなく触らないもののほうです。「この関数のシグネチャは変えない」「公開APIは維持する」「テストファイルは追加のみ」。改修の事故は、頼んでいない範囲に手が伸びたときに起きます。

計画が固まると、承認の選択肢が出ます。自動で進めるか、編集ごとに手動承認するか、計画を練り直すか。プロジェクトで毎回planから始めるなら .claude/settings.json に既定モードを書けます。

{
  "permissions": {
    "defaultMode": "plan"
  }
}

ただし計画は無料ではありません。1文で差分を説明できる規模、たとえば変数名の変更やログ1行の追加なら、計画を挟むより直接頼んだほうが速く終わります。計画が効くのは、複数ファイルにまたがるときと、対象のコードを自分がよく知らないときです。モードの細部はClaude Code Planモード完全ガイドにまとめてあります。

手順4: 小さく変えて、都度検証して、都度コミットする

改修は小さくテスト可能な単位に割って進めます。ここでのコミット境界は「区切りがよさそうなところ」ではなく、検証が通った時点です。通っていない状態をコミットに含めると、あとで二分探索ができなくなります。

1回の指示に載せる変換は1種類に絞ります。「型を整理しつつ命名も直して依存も差し替える」と頼むと、失敗したときにどれが原因か分かりません。指示のなかに検証まで含めてしまうのが確実です。

invoice.ts の calculateTax から税率の解決部分だけを関数に抽出してください。
シグネチャと戻り値は変えないでください。
抽出後に npm test -- invoice を走らせ、失敗したら通るまで直してください。

同じ問題で2回訂正しても直らないときは、指示を足すのをやめます。その時点で会話は失敗した試行で埋まっており、Claudeはそれも文脈として読んでいます。/clear で流し、分かったことを織り込んだ指示を書き直したほうが、長い会話を続けるより結果が良くなります。

セッションを捨てたくないときは部分的に畳めます。/compact 改修対象のファイル一覧とテストコマンドを残して のように焦点を指定して圧縮する方法が1つ。/rewind のメニューから特定のメッセージを選び、「そこから先」または「そこまで」を要約する方法がもう1つです。どちらも要約であって、ファイルには触りません。長い改修セッションを保たせるのはこのあたりの操作です。

手順5: 書いていない文脈の目で差分を点検する

改修した本人が改修を採点すると、通ります。書いた側の文脈には「なぜそうしたか」が残っているので、抜けが抜けとして見えません。差分だけを見る別の文脈が要ります。

手元でそれをやるのが /code-review です。ブランチのupstreamから先のコミットと未コミットの変更を対象に、不具合と、再利用・簡素化・効率の観点での指摘を返します。対象は明示もできます。ファイルパス、PR番号、ブランチ名、main...my-feature のような範囲指定。

/code-review main...refactor/invoice

--fix を付けると指摘を作業ツリーに適用し、--comment でPRのインラインコメントとして投稿します。ここで気をつける点が1つあります。レビューはバックグラウンドのサブエージェントとして走るため、--fix が加えた編集はセッションのチェックポイントの外です。/rewind では戻りません。戻すときはgitを使います。

指摘の量はeffortレベルで調整できます。lowmedium は自信の高いものだけを返すので誤検知が減り、high 以上は網を広げます。改修の初回は低めから始めて、指摘の質を見て上げるほうが扱いやすいです。

サブエージェントに自分でレビュー観点を渡す形もあります。計画に対する充足を見たいならこちらです。

サブエージェントで invoice の改修差分を PLAN.md と突き合わせてください。
計画にある項目がすべて実装されているか、
触らないと決めた範囲に変更が入っていないかを見て、
スタイルの好みではなく抜けだけを報告してください。

ここには落とし穴があります。抜けを探せと言われたレビュアーは、問題のない差分に対してもたいてい何かを報告します。それを全部拾うと、抽象化の層が増え、起こり得ないケースの防御コードとテストが積み上がる。正しさと明示した要件に関わる指摘だけを直し、残りは任意扱いにする線引きを最初に伝えておくと荒れません。

改修の規模別に、どこまで用意するかの早見表

準備の重さは改修の規模に比例させます。全部に安全網を張ると着手が遅れ、何も張らないと差分が信用できません。

改修の規模事前に用意するもの検証の締め方plan mode
1関数の整形・改名事前に用意するもの既存テストが通る確認だけ検証の締め方同じ指示のなかで1回走らせるplan modeほぼ不要
1ファイル内の構造変更事前に用意するもの触る境界のテスト1〜2本検証の締め方指示に「通るまで直す」を含めるplan modeあると安全
モジュールをまたぐ抽出事前に用意するもの境界テスト+読み込み拒否ルール検証の締め方/goal の完了条件で締めるplan mode必要
数百ファイルの一括変換事前に用意するもの変換手順の文書と2〜3件の試行検証の締め方1件ごとにOK/FAILを返させるplan mode計画は別ファイルに

/goal は完了条件を別の評価器が毎ターン確認し、条件が満たされるまでClaudeを走らせ続ける仕組みです。決定的に締めたいなら、Stop hookでテストコマンドをゲートにする手もあります。この2つの使い分けはClaude Codeでテスト駆動開発を回す手順で扱っています。

数百ファイルへ同じ変換をかけるときの進め方

同じ変換を大量のファイルにかける改修は、1つのセッションで回すと途中でコンテキストが尽きます。非対話モードでファイル単位に分ける形が向いています。

for file in $(cat files.txt); do
  claude -p "Migrate $file from React to Vue. Return OK or FAIL." \
    --allowedTools "Edit,Bash(git commit *)"
done

順序が肝心です。先に対象ファイルの一覧をClaudeに作らせ、まず2〜3件だけ流す。そこで出た失敗を見てプロンプトを直してから全件に広げます。--allowedTools で使えるツールを絞るのは、人が見ていない実行では効きます。結果を機械的に扱いたいなら --output-format jsonresult フィールドを受け取れます。

並列で走らせるならworktreeで作業ツリーごと分けます。巨大リポジトリでは worktree.sparsePaths に必要なディレクトリだけを並べると、チェックアウトが軽くなります。

worktreeを軽くする設定
{
  "worktree": {
    "sparsePaths": [
      ".claude",
      "packages/api",
      "packages/shared"
    ],
    "symlinkDirectories": [
      "node_modules"
    ]
  }
}

sparsePaths に並べるのはディレクトリです。package.json やロックファイルのようなルート直下のファイルは常にチェックアウトされます。一方でルート直下のディレクトリは含まれないため、リポジトリルートの .claude/ を使いたいならリストに書きます。symlinkDirectories は各worktreeの node_modules/ を本体へのシンボリックリンクにして、ディスク上の重複を避けます。

権限プロンプトを止めたいときはauto modeという選択肢もあります。ただし使えるアカウントには条件があります。Anthropic APIとClaude Platform on AWSではOpus 4.6以降・Sonnet 4.6以降・Fable 5、Amazon BedrockとGoogle CloudのAgent Platform・Microsoft Foundry・サインイン済みのClaude appsゲートウェイではSonnet 5・Opus 4.7以降・Fable 5のみが対象で、Sonnet 4.5やOpus 4.5、Haikuはどのプロバイダでも対象外です。条件を満たしている前提で見ると、分類器が実行前のコマンドを見て、依頼の範囲を超えるものや未知のインフラを対象にするものを止めます。ただし -p の非対話実行では、分類器が繰り返しブロックすると中断します。人が答えられないためです。

よくあるつまずき

「このファイルをきれいにして」と投げると、範囲が無いので探索が止まりません。コンテキストが埋まったところで質が落ちます。対象・変えない約束・完了の判定を先に書けば起きない事故です。

テストを「正しい仕様」で書かせるのも同じ種類の失敗です。既存の挙動と食い違い、改修とバグ修正が1つの差分に混ざります。先に固定するのは現状で、直すのはその後。

/rewind で戻ったつもりになるのは、気づくのが遅れる分だけ厄介です。Bashコマンドが加えた変更、サブエージェントの編集、シンボリックリンク先は戻りません。改修中はgitのコミットを退避先にします。

CLAUDE.md に改修方針を全部書くと、毎セッション読み込まれるファイルが膨らみ、Claudeは半分を無視します。その改修のときだけ必要な知識はスキルに置きます。

巨大な差分を一度にレビューへ出すのも避けたいところです。指摘が量として返ってきて、どれが本質か判断できなくなります。検証が通った単位でコミットし、その単位でレビューを回すほうが速く終わります。

うまく進まないときの切り分けは、改修に固有の話とデバッグ全般の話が混ざりがちです。原因特定の順序はClaude Codeデバッグの精度は、渡す情報の質で決まるを参照してください。Claude Code全体の使い方はClaude Code(クロードコード)とはにまとめてあります。

よくある質問

テストが1本も無いコードでも改修を任せられますか

任せられますが、最初の指示はテストではなく「現状の振る舞いを固定する何か」を作らせることになります。テストフレームワークが無いリポジトリなら、対象を呼び出して出力を標準出力に吐き、期待値ファイルと diff を取るシェルスクリプト1本でも役割を果たします。合否が終了コードで返ればClaudeはそれを読めます。

plan modeは毎回必要ですか

差分を1文で説明できる規模なら、計画を挟むほうが遅くなります。plan modeが効くのは、複数ファイルにまたがる変更、方針が定まっていない変更、対象のコードを自分がよく知らない変更です。改修は3つ目に当たることが多いため、既存コードを触るときは使う側に倒しても大きくは外れません。

Claudeが加えたリファクタリングを丸ごと取り消したいときは

Claudeのファイル編集ツールによる変更だけなら /rewind で戻せます。Bashコマンド経由の変更、サブエージェントが加えた編集、--fix 付きの /code-review が適用した編集はチェックポイントの外なので、gitで戻します。改修に入る前に作業ツリーをコミット済みにしておくと、この判断が要らなくなります。

リファクタリングとバグ修正を同時に頼んでもいいですか

分けたほうが差分の意味がはっきりします。振る舞いを変えない変更と変える変更が同じコミットに入ると、テストが落ちたときにどちらが原因か切り分けられません。現状固定のテストを書く段階でおかしな挙動に気づいた場合は、そのまま固定して改修を終え、別の変更として直す順序が扱いやすいです。

大きなリポジトリですぐコンテキストが埋まります

読ませる量と、読んだ結果が置かれる場所の2つを分けて考えます。量は permissions.denyRead ルールとコードインテリジェンスプラグインで減らします。置き場所は調査をサブエージェントへ出すことで変わります。探索で読んだファイルの中身はサブエージェント側のコンテキストに留まり、要約だけがメインの会話に返ります。それでも足りないなら、起動位置そのものを見直す段階です。

レビューはどのタイミングで挟むと効きますか

検証が通ってコミットできる単位ができたときです。改修が全部終わってからまとめてレビューに出すと、指摘の量が判断できる限界を超えます。/code-review はブランチのupstreamから先の差分を見るため、コミットを刻んでおけばその単位で回せます。

まとめ

Claude Codeでのリファクタリングは、指示の巧拙より準備で決まります。読ませない範囲を切り、現状の振る舞いを固定し、計画で触らないものを明示し、検証が通った単位でコミットし、別の文脈に差分を見せる。この5つが揃っていれば、テストの無いコードでも改修は進みます。

逆に言えば、どれか1つでも欠けた状態で「きれいにして」と投げると、差分の正しさを人間が全部読んで判断することになります。それは改修を速くしません。安全網を張る時間は、改修そのものの時間から引かれるのではなく、レビューの時間から引かれます。

この記事を共有:XはてブLinkedIn