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

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

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

Claude Codeでのリファクタリングが荒れるとき、原因は変更の中身より手前にあります。読ませる範囲が広すぎる、振る舞いを固定するものが何も無い、一度の指示で触る範囲が大きすぎる。この3つのどれかです。ここではテストが揃っていない既存コードを前提に、着手前に何を用意し、どの順番で何を渡すと改修が安全に進むかを手順としてまとめます。同じ「検証手段を先に用意する」発想は、企業規模の近代化プロジェクトでも証明書・昇格ポリシーという形で使われています(Claude Codeのコード近代化を着手前に準備する6ステップを参照)。

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

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

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

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

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

必要なものは3つだけです。git上できれいな作業ツリー、対象の振る舞いを確認できるコマンド1つ、そしてビルドとテストの手順が書かれたCLAUDE.md です。どこから手を付けるか迷うときは、tech-debtスキルによる負債の棚卸しで優先順位を付けておく手があります。

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

くらべる

/rewindで戻るもの・戻らないもの

チェックポイントの対象

戻る

Claudeのファイル編集ツールによる変更です。フォアグラウンドで走る、context: fork 付きのスキルが加えた編集も、自分のターン中の編集として戻ります。

gitで戻す

戻らない

  • rm や mv などBashコマンドが加えた変更
  • サブエージェントの編集(バックグラウンドで動く分岐スキルや、/code-review --fix を含む)
  • シンボリックリンク・ハードリンクのパス(Restored the code, but skipped N files の警告が出ます)

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

/init
/context

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

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

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

こちらは permissions.deny の Read ルールで塞ぎます。

.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とPlanはCLAUDE.md と親セッションのgitステータスを読み込まない設計なので、プロジェクト固有の約束事を前提にした調査を頼むときは、その前提をプロンプトに書き添えます。範囲を切らずに「調べて」と投げると数百ファイルを読んでコンテキストが埋まります。モノレポでの起動位置や除外設計はClaude Codeモノレポ設計で詳しく扱っています。

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

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

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

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

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

検証手段はテストである必要すらありません。会話のなかでClaudeが読み取れる合否を返すものなら、役割は同じです。

検証

テストの代わりになる検証手段

  • ビルドの終了コード

    型エラーや参照切れを、0か非0かで返します。

  • リンター

    規約違反や未使用の変数を、行番号つきで返します。

  • 出力とフィクスチャの突き合わせ

    改修前の出力を保存しておき、改修後の出力と diff を取ります。

  • スクリーンショット比較

    画面の見た目が改修対象のときに、前後の画像を並べて判定します。

テストの器すら無いリポジトリなら、終了コードが返るシェルスクリプト1本から始めても成立します。対象を呼び出した出力を、改修前に保存した期待値ファイルと突き合わせる形です。

#!/bin/sh
# 改修前に一度だけ: node scripts/run-tax.js > expected.txt
node scripts/run-tax.js | diff - expected.txt

diff は差が無ければ終了コード0、あれば1を返すので、Claudeは「通った・落ちた」をそのまま読めます。期待値ファイルは、おかしな出力も含めて現状のまま保存します。

そして全体を覆おうとしないことです。改修で触る境界の内側と外側、そこだけ押さえれば足ります。カバー率を上げる作業は、それ自体が別のプロジェクトになります。入力の洗い出しと乱数・日時の固定はClaude Codeで特性テストを書く手順にあります。

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

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

入り方は3つあります。セッション中はShift+Tab でモードを循環させる、単発なら /plan をプロンプトの先頭に付ける、起動時から入るならフラグを渡します。auto modeで始まったセッションでは、最初のShift+Tabでmanualに切り替わり、その後 acceptEdits、plan の順に進みます。ステータスバーに ⏸ plan mode on が出ていれば入れています。

起動フラグに渡せる値は、v2.1.287の claude --help に次のように出ます。

claude --version
2.1.287 (Claude Code)
 
claude --help
  --permission-mode <mode>   Permission mode to use for the session
                             (choices: "acceptEdits", "auto",
                             "bypassPermissions", "manual",
                             "dontAsk", "plan")

plan を選ぶなら、次のように起動します。

claude --permission-mode plan

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

計画が固まると、承認の選択肢が出ます。

  • Yes, and use auto mode: 承認してauto modeで進めます。auto modeが使えないセッションではYes, auto-accept editsという表示になります
  • Yes, manually approve edits: 編集を1件ずつ確認しながら進めます
  • No, keep planning: plan modeに留まり、計画の修正点を伝えます

プロジェクトで毎回planから始めるなら .claude/settings.json に既定モードを書けます。

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

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

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

改修は小さくテスト可能な単位に割って進めます。1回の指示に載せる変換は1種類に絞ります。「型を整理しつつ命名も直して依存も差し替える」と頼むと、失敗したときにどれが原因か分かりません。数千行の関数を割る場合の抽出基準は巨大関数の分割手順にあります。循環importの解消のように件数で進捗が測れる改修は、madgeで1件ずつ回す手順が具体例になります。

手順

改修1サイクルの回し方

  1. 1

    変換を1種類だけ頼む

    検証までを同じ指示に含めます。下のプロンプトがその形です。

  2. 2

    検証が通るまで直させる

    テストコマンドの失敗は、Claudeがその場で読んで直します。

  3. 3

    通った時点でコミットする

    区切りがよさそうなところではなく、検証が通った時点が境界です。通っていない状態をコミットに含めると、あとで二分探索ができなくなります。

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

同じ問題で2回訂正しても直らないときは、指示を足すのをやめます。その時点で会話は失敗した試行で埋まっており、Claudeはそれも文脈として読んでいます。/clear で流し、分かったことを織り込んだ指示を書き直したほうが、長い会話を続けるより結果が良くなります。原因の特定そのものに手間取るときは、Claude Codeデバッグの精度は、渡す情報の質で決まるの順序が使えます。

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

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

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

手元でそれをやるのが /code-review です。現在の差分、またはPR番号・ブランチ名・ファイルパスで指定した対象を、不具合の観点でレビューします。モデルとeffortレベルによっては、再利用や簡素化の指摘も加わります。ブランチ名には main...my-feature のような範囲指定も使えます。

/code-review main...refactor/invoice

--fix を付けると指摘を作業ツリーに適用し、--comment でPRのインラインコメントとして投稿します。effortは low から max まで選べて、ultra はクラウドで走る深いレビューです。バックグラウンドで走る --fix の編集は、セッションのチェックポイントの外です。/rewind では戻りません。戻すときはgitを使います。フォアグラウンドで走るレビューの編集は、通常どおり /rewind で戻せます。

シグネチャや公開APIを変えた回は、後方互換性の確認をレビューに明示的に組み込む必要があります。変更した関数の呼び出し元まで確実に見てもらうには、呼び出し元を先に grep などで洗い出し、対象ファイルとして併せて渡すほうが確実です。あるいは次のようにサブエージェントへ確認自体を頼みます。

サブエージェントで calculateTax のシグネチャ変更が全呼び出し元と整合しているか確認してください。
grepで呼び出し元を洗い出し、引数の数・型・戻り値の扱いにずれが無いかを1件ずつ見て、
問題があればfile:line付きで報告してください。

手順3のplan modeで「シグネチャは変えない」と決めていた場合も、この確認は省けません。決めた制約が守られたかどうかは、計画時点ではなく差分ができてから検証する対象だからです。

同じ要領で、計画に対する充足を見ることもできます。

サブエージェントで 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つのセッションで回すと途中でコンテキストが尽きます。非対話モードでファイル単位に分ける形が向いています。COBOLなど基幹システム全体の移行は、Claude Codeのコードモダナイゼーションで扱っています。規則で書き切れる置換なら、codemodを書かせてfixtureで検証する方法もあります。

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 json で result フィールドを受け取れます。

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

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

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

権限プロンプトの扱いは、v2.1.283以降のターミナルでは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はどのプロバイダでも対象外で、その場合はmanualで始まります。

条件を満たしていれば、分類器が実行前のコマンドを見て、依頼の範囲を超えるものや未知のインフラを対象にするものを止めます。-p の非対話実行は、通常はdefaultで始まります。feature flagを取得しない環境では、v2.1.285以降でautoになります。autoで走らせた -p はブロックが繰り返されて閾値に達しても止まらず、ブロックされた操作が走らないまま、Claudeが作業を続けます。一括変換の途中で意図した操作が通っていないことに気づけるよう、戻り値のOK/FAILだけでなく結果の差分も見ておきます。

まとめ

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

安全網を張る時間は、改修そのものの時間から引かれるのではなく、レビューの時間から引かれます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/common-workflows
  2. code.claude.com/docs/en/best-practices
  3. code.claude.com/docs/en/large-codebases
  4. code.claude.com/docs/en/permission-modes
  5. code.claude.com/docs/en/checkpointing
  6. code.claude.com/docs/en/sub-agents
  7. code.claude.com/docs/en/code-review
  8. code.claude.com/docs/en/memory
  9. code.claude.com/docs/en/commands
  10. code.claude.com/docs/en/goal
この記事を共有:XはてブLinkedIn