数千行をリファクタリングで消す
Claude Codeのリファクタリングで古いモジュールを消すなら、呼び出し側から順に。依存の向きの調べ方とコミットの切り方をまとめた。
古い仕組みを丸ごと消すリファクタは、足すリファクタより怖いものです。エージェントに「この古いモジュールを消して」と頼むと、消すこと自体はすぐ終わります。困るのは、どこから消したかで途中の状態が壊れ、1つの大きなコミットに全部が混ざることです。この記事では、数千行規模の削除を、壊さずに小さく進める順番をまとめます。手順は、手元のダミーのリポジトリで動かして確かめました(Python 3.13.5、git 2.47.3、2026-10-16)。再現は数行のダミーのコードで行ったもので、数千行の規模では測っていません。
消す前に、依存の向きを出す
最初にやるのは、消したいものが「誰に使われ、誰を使っているか」の一覧を作ることです。消す対象の中にも上下関係があります。
手元のダミーの構成はこうです。古い整形モジュール(legacy_fmt)が、さらに古い下請けのモジュール(legacy_core)を使い、その整形モジュールを2つの画面相当のモジュールが呼んでいます。置き換え先の新しいモジュールは、すでにあります。
import 文から依存を抜き出し、Python 標準の graphlib で並べました。
== 依存グラフ(import から抽出)と削除順
legacy を使う側: ['invoice', 'report']
消す順(consumer→provider): 呼び出し側の置き換え → legacy_fmt → legacy_core
消す順は、使う側(consumer)から、使われる側(provider)へ。依存は一方向に流れているので、上流から外していけば、どの時点でもコードは閉じた状態を保てます。
依存の一覧は、エージェントに作らせてかまいません。ただし「調べた結果」をそのまま信じず、grep やスクリプトで同じ一覧を出し直して照らします。import の grep で拾えない使われ方もあるので、その点は後で触れます。
逆から消すと、途中が壊れる
エージェントは、目立つもの、つまり一番下の古い部品から消しにいきがちです。手元で、下請けのモジュールから先に消してテストを回しました。
== 失敗例: provider から先に消す
FAILED (errors=1)
使う側が残っているので、import の時点で落ちます。小さな例では直せば済みます。規模が大きいとエラーが多くの箇所に散りやすく、エージェントはそれを一つずつ「修正」し始めます(編集部の経験で、ここでは測っていません)。その修正が、消すはずだったコードを別の場所に書き写すことだったりします。
1ステップ1コミットで進める
正しい順番で、1ステップごとにテストを回し、コミットを切りました。
== 手順1: 呼び出し側を new_fmt に置き換え
OK
== 手順2: legacy_fmt を削除
OK
== 手順3: legacy_core を削除
OK
28c1d90 step3
1 file changed, 2 deletions(-)
9648f75 step2
1 file changed, 3 deletions(-)
9bd2477 step1
2 files changed, 2 insertions(+), 2 deletions(-)
最初のコミットは置き換えだけ、後のコミットは削除だけ、という形になります。どのコミットでもテストが通るので、途中で問題が見つかっても、そのコミットだけを戻せます。レビューする側も、「置き換えが正しいか」と「消したものが本当に使われていないか」を、別々に見られます。
公式のドキュメントも、リファクタは小さく、テストできる単位で進めるよう勧めています(出典:https://code.claude.com/docs/en/common-workflows 、2026-10-16確認)。
並列に分けるときの注意
Claude Code には、大きな変更を並列で進める /batch があります。公式の説明では、コードベースを調べて作業を5〜30の独立した単位に分け、計画を示し、承認されると単位ごとに分離された worktree でバックグラウンドのサブエージェントを走らせ、それぞれがテストを回して変更を出します(出典:https://code.claude.com/docs/en/commands 、2026-10-16確認)。
ここで大事なのは「独立した単位」という条件です。削除のリファクタは、上で見たように順番に依存しています。使う側の置き換えが終わる前に、使われる側の削除が別の単位で走ると、それぞれの worktree では緑でも、合わせた途端に壊れます。並列にするなら、同じ段(たとえば「呼び出し側の置き換え」)の中だけを分けます。
エージェントへの頼み方
編集部では、削除のリファクタを次の文面で頼んでいます。
次のモジュールを削除する。いきなり消さないこと。
- まず、削除対象を使っている箇所と、削除対象どうしの依存の一覧を作る。
- 消す順は、使う側から使われる側へ。
- 1ステップごとにテストを回し、1コミットにする。置き換えと削除は別のコミットにする。
- テストが落ちたら、その場で別の場所に書き写して直さない。止めて報告する。
最後の行は、上で書いた「書き写して直す」を防ぐためのものです。
削った量は、始点からの差分で数える
「何行消えたか」は、作業の始点のコミットと比べて数えます。手元のダミーのリポジトリで、3つのステップをまとめて見ました。
$ git diff --shortstat HEAD~3 HEAD
4 files changed, 2 insertions(+), 7 deletions(-)
$ git diff --name-status HEAD~3 HEAD
M app/invoice.py
D app/legacy_core.py
D app/legacy_fmt.py
M app/report.py
D が付いたファイルは丸ごと消え、M は書き換えです。削除のリファクタで A(追加)が増えていたら、消したはずのコードがどこかに書き写されていないかを疑います。行数だけでなく、この内訳まで見ると、エージェントの報告と食い違ったときにすぐ気づけます。
消したあとに確かめること
テストが全部通っても、削除が正しかった証拠にはなりません。テストが、消したものを最初から検査していないことがあるからです。
まず、消した名前がどこにも残っていないかを、コード全体で grep します。次に、import の grep だけで「未使用」と決めないことです。CSS やビルド設定、package.json の scripts からしか呼ばれていないものもあります。その確かめ方と、テストが全部通ったのに誤削除だった例は「緑のテストは何も証明しない」に書きました。
もう一つ、エージェントの「削除が完了しました」も、そのまま信じません。削った行数やコミットの数は、報告の文ではなく git log --shortstat のような実数で確かめます。報告と実数を機械で突き合わせる仕組みは「AIの「完了しました」を信じない仕組み」にまとめました。
まとめ
- 消す前に、削除対象の依存の向きを出す。エージェントの調査結果は、スクリプトで出し直して照らす。
- 消す順は、使う側から使われる側へ。逆から消すと途中が壊れ、書き写しの「修正」が始まる。
- 1ステップ1コミット。置き換えと削除は別のコミットにする。
/batchのような並列化は、独立した単位にだけ使う。順番に依存する段をまたがせない。- テストの緑と「完了しました」は証拠にならない。grep と git の実数で確かめる。