CodeCraft Lab

リファクタリングを安全に進める

影響範囲の特定からリスク評価、段階的な分割戦略、検証手順まで、リファクタリングを体系的に計画・実行する方法を学びます

実践24分で読了
リファクタリングテスト駆動リスク管理ワークフロー

このレッスンで学ぶこと

  • リファクタリング着手前に影響範囲を特定し、リスクを評価する方法
  • テスト駆動でリファクタリングを進めるワークフロー
  • 大規模リファクタリングを安全に分割する戦略と、diffを都度確認する安全弁の作り方

前提条件

  • Lesson 1「コードレビューをClaude Codeに任せる」を読んでいること(差分レビューの基本操作を前提とします)
  • 対象プロジェクトにテストランナー(Jest, Vitest, pytest等)がセットアップされていること

なぜ「いきなり直す」と失敗するのか

「このファイルをリファクタリングして」と一言で依頼すると、Claude Codeは真面目にファイル全体を作り直そうとすることがあります。小さなユーティリティ関数ならそれでも問題ありませんが、複数のモジュールから参照されているコアロジックだと話が変わります。

リファクタリングが事故につながる典型的なパターンは次の3つです。

  1. 影響範囲を把握しないまま着手する — 変更した関数が想定外の場所から呼ばれていて、別の機能が壊れる
  2. 一度に変更範囲が大きすぎる — どの変更が問題を引き起こしたか特定できなくなる
  3. 検証が「動いているように見える」止まり — テストを実行せず、目視確認だけでマージしてしまう

このレッスンでは、これら3つの失敗パターンに対応する形で、計画 → 実行 → 検証のサイクルを体系的に組み立てます。実践レシピ「レガシーコードを安全にリファクタリングする」ではテストの有無に応じた具体的な手順を扱いましたが、ここではその前段階である「計画の立て方」と、リファクタリングを安全に運用するための仕組みづくりに焦点を当てます。

ステップ1: リファクタリング計画を立てる

影響範囲を特定する

コードを変更する前に、まず「何が影響を受けるか」を可視化します。Claude Codeはコードベース全体を検索できるため、人手で grep するより網羅的に依存関係を洗い出せます。

src/utils/priceCalculator.ts のcalculateTotalPrice関数について、
以下を調査して報告して:
 
- この関数を呼び出している箇所をすべて列挙する(ファイル名:行番号)
- それぞれの呼び出し元が、戻り値や副作用にどう依存しているか
- この関数が依存している外部モジュール・グローバル状態

調査結果は、リファクタリングの「変更してはいけない境界」を決める材料になります。呼び出し元が多いほど、外部インターフェース(関数シグネチャ、戻り値の型)は変えずに内部実装だけを変える方針が安全です。

サブエージェントで調査をメインコンテキストから分離する

影響範囲の調査は、ファイルを大量に読み込むためコンテキストを消費しがちです。「サブエージェントを使って影響範囲を調査して」と指示すると、調査結果のサマリーだけがメインの会話に返り、実装フェーズのコンテキストを圧迫しません。

リスクを評価する

影響範囲がわかったら、変更のリスクを評価します。リスク評価の軸として、以下の3点を確認するとよいでしょう。

評価軸確認内容高リスクの例
呼び出し元の数変更対象の関数・モジュールが何箇所から参照されているか10箇所以上から呼ばれるユーティリティ関数
テストカバレッジ変更対象に既存テストがあるか、あればどの程度の網羅性かテストが存在しない、または正常系のみ
副作用の有無DB書き込み、外部API呼び出し、グローバル状態の変更を伴うか決済処理、ファイルI/O、キャッシュ操作
src/services/orderService.ts のprocessOrder関数について、
リスク評価をして:
 
- 呼び出し元の数と分布
- 既存テストの有無とカバレッジ(正常系/異常系/境界値)
- DB書き込みや外部API呼び出しなどの副作用
 
評価結果をもとに、このリファクタリングのリスクレベル(高/中/低)を判定して、
判定理由も添えて

リスクが「高」と判定された場合は、後述する段階的分割戦略を必ず適用しましょう。「低」であれば、テストさえ整っていれば一気に進めても問題ないことが多いです。

計画をドキュメント化する

調査とリスク評価の結果をもとに、Claude Codeに具体的な計画を立てさせます。ここで重要なのは、計画自体をファイルに書き出させることです。会話の中だけで計画を立てると、後から「なぜこの順序にしたのか」を追跡できません。

これまでの調査・リスク評価をもとに、processOrder関数の
リファクタリング計画をPLAN.mdに書き出して。
 
含める項目:
- 現状の問題点
- リファクタリングのゴール(外部インターフェースは変更しない)
- ステップごとの変更内容と、各ステップの完了条件
- 各ステップのリスクレベル
- ロールバック方針(問題が起きた場合にどう戻すか)
Plan Modeで計画段階を分離する

計画を立てる段階では、まだコードを変更したくないことがほとんどです。claude --permission-mode plan でPlan Modeに入ると、Claude Codeはファイルを読み取って計画を提案しますが、実際の編集は行いません。計画の内容に納得してから通常モードに切り替えると、「計画と実装が混ざって収拾がつかなくなる」事態を防げます。

ステップ2: テスト駆動でリファクタリングを進める

計画ができたら、実装フェーズに入ります。リファクタリングにおけるテスト駆動とは、「先にテストで現状の振る舞いを固定してから、内部実装を変える」という進め方です。

flowchart TD
    A["対象コードの現在の<br/>振る舞いをテストで固定する"]
    B["テストが全てパス<br/>することを確認する"]
    C["内部実装を変更する<br/>(外部インターフェースは維持)"]
    D["テストを再実行する"]
    E{"全てパス?"}
    F["変更を確定(コミット)"]
    G["原因を調査し、<br/>実装を修正 or 計画を見直す"]
 
    A --> B --> C --> D --> E
    E -->|Yes| F
    E -->|No| G --> C
 
    style A fill:#e8f4fd,stroke:#1a73e8
    style F fill:#e8f5e9,stroke:#2e7d32
    style G fill:#fde8e8,stroke:#c0392b

振る舞いを固定するテストを書く

既存テストが不十分な場合、リファクタリング対象の「現在の振る舞い」を保証するテストを先に追加します。これは新機能のテストとは目的が異なり、バグも含めて現状の動作を記録することが目的です。

src/services/orderService.ts のprocessOrder関数の
現在の振る舞いを保証するテストを書いて。
 
注意点:
- リファクタリングの「前後で結果が変わらないこと」を検証する目的
- 正常系だけでなく、現在の異常系の挙動(エラーの投げ方など)も記録する
- 意図的なバグらしき挙動を見つけても、今は修正せず、まずテストとして記録する
バグの修正とリファクタリングを混ぜない

既存コードのバグに気づいても、リファクタリングと同時に直さないようにしましょう。バグ修正は「振る舞いを変える」変更であり、リファクタリングの「振る舞いを変えない」という前提と矛盾します。バグはバックログに記録し、別のタスクとして対応するのが安全です。

最小単位で実装し、都度テストを通す

計画のステップを1つずつ実施します。各ステップは「テストが通る」という明確な完了条件を持たせましょう。

PLAN.mdのステップ1を実施して:
processOrderからバリデーションロジックをvalidateOrder関数として抽出する。
 
制約:
- processOrderの外部インターフェース(引数・戻り値・例外)は変更しない
- 抽出後、既存テストとリファクタリング前に追加したテストの両方を実行する
- 1つでも失敗したら、原因を報告して。次のステップには進まない

「次のステップには進まない」という制約を明示するのがポイントです。指定しないと、Claude Codeはテストの失敗を放置したまま複数ステップを一気に進めてしまうことがあります。

ステップ3: 大規模リファクタリングを段階的に分割する

数百行の関数や、複数モジュールにまたがる変更では、ステップの粒度設計そのものが成功の鍵を握ります。

分割の基本原則

  • 1ステップ = 1つの責務の抽出 複数の関心事を同時に動かさない
  • 各ステップは独立してテスト可能 ステップ単体でテストが書け、パスを確認できる粒度にする
  • 依存順に並べる 後続のステップが前のステップの成果物に依存する場合、その順序を守る
src/services/order-service.ts のprocessOrder関数(230行)を
分割するリファクタリング計画を立てて。
 
現状、この関数はバリデーション・在庫確認・価格計算・注文保存・通知送信を
すべて1つの関数で行っている。
 
分割方針:
- 各責務を独立した関数として抽出する
- 抽出の順序は、依存関係が少ないものから着手する
- 各ステップは単体でテスト可能な粒度にする
- ステップごとに、想定される副作用とリスクレベルを明記する

スコープを明示的に制限する

段階的に進める際、最も事故が起きやすいのは「触れてはいけない範囲」が曖昧なときです。各ステップの指示で、明示的に対象外を伝えましょう。

processOrder関数のリファクタリング ステップ2:
在庫確認ロジックをcheckInventory関数として抽出して。
 
制約:
- ステップ1で抽出したvalidateOrder関数には触れない
- 価格計算・注文保存・通知送信のロジックには触れない
- checkInventory関数のシグネチャは (orderId: string, items: OrderItem[]) => Promise<boolean> とする
「変更しない範囲」を毎回書くのは冗長では?

毎ステップで制約を書き直すのは手間に感じるかもしれませんが、これがリファクタリングの事故を防ぐ最大の安全弁です。CLAUDE.mdに「リファクタリング作業時は、指示されたスコープ外のファイル・関数を変更しない」という一般原則を書いておくと、個別の指示を多少簡略化できます。

diffを都度確認する安全弁を作る

段階的に進めていても、「気づいたら想定外の範囲まで変更されていた」という事態は起こり得ます。これを防ぐための安全弁を仕組みとして用意しましょう。

ステップごとにdiffを要約させる

各ステップの実装が終わったら、コミットする前に必ず差分を確認します。

今回の変更の差分を確認して、以下を報告して:
- 変更されたファイルと、それぞれの変更行数
- 当初の指示範囲(checkInventory関数の抽出)を超える変更がないか
- 既存のテストとリファクタリング用に追加したテストが全てパスしているか
 
範囲外の変更が見つかった場合は、その内容と理由を報告して。
私が確認するまでコミットしないで

「私が確認するまでコミットしないで」という一文は、Claude Codeに自動でコミットまで進ませず、人間の確認ポイントを必ず挟むための明示的な指示です。

Hookで機械的なチェックを強制する

プロンプトでの指示は強力ですが、確実性に欠けることもあります。より確実に安全弁を効かせたい場合は、Hooksを使って機械的にチェックを強制する方法もあります。

ファイル編集後に自動でテストを実行し、
失敗した場合は変更を警告するフックを書いて

Hookはプロンプトと異なり決定論的に動作するため、「テストを実行し忘れる」という人為的・AI起因のミスを構造的に防げます。Hooksの詳細な設定方法は応用コースで扱います。

チェックポイント機能はGitの代わりにならない

Claude Codeにはセッション内の変更を巻き戻せるチェックポイント機能(Escを2回押す、または /rewind)がありますが、これはClaude Codeが行った変更のみを追跡する仕組みです。Gitのコミット履歴の代替にはならないため、リファクタリングの各ステップは引き続きこまめにコミットしておきましょう。

ステップ4: リファクタリング後の検証手順

すべてのステップが完了したら、最終的な検証を行います。テストが通ることはもちろん重要ですが、それだけでは不十分な場合もあります。

自動テストを全て実行する

リファクタリング対象だけでなく、プロジェクト全体のテストスイートを実行し、意図しない箇所への影響がないか確認します。

プロジェクト全体のテストスイートを実行して。
失敗があれば、今回のリファクタリングとの関連性を調査して報告して

リファクタリング前後の出力を比較する

特に重要なロジックでは、テストに加えて「同じ入力に対して同じ出力が返るか」を直接比較すると安心です。

リファクタリング前後でprocessOrder関数の実行結果が
同一であることを確認するテストを書いて。
代表的な入力パターンを5つ用意して、
出力と副作用(DB書き込み内容、通知送信の有無)が
一致することをアサーションして

第三者視点でのレビューを依頼する

実装した本人(あるいは実装させた本人)は、変更に対して無意識のバイアスを持ちがちです。新しいコンテキストでのレビューを挟みましょう。

サブエージェントを使って、今回のリファクタリングの全体差分をレビューして。
以下を確認して:
- 振る舞いが本当に変わっていないか
- PLAN.mdに記載した計画から逸脱していないか
- 新しく導入した関数の命名・責務分離は適切か

Lesson 1で学んだレビュー観点の構造化を、ここでも応用できます。

計画と結果の差分を記録する

計画通りに進まなかった部分があれば、その理由を記録しておくと、次回以降のリファクタリング計画の精度が上がります。

PLAN.mdに「実施結果」セクションを追記して。
計画と異なった点があれば、その理由と対応も記録して

よくある質問

Q. 小さな関数1つのリファクタリングでも、ここまで丁寧にやる必要がありますか?

いいえ。このレッスンの手順は「リスクが高い変更」を想定したフルセットです。呼び出し元が1〜2箇所しかない小さな関数であれば、影響範囲調査と最終検証だけで十分なことが多いです。リスク評価のステップで「低リスク」と判断できれば、計画書の作成や段階分割は省略して構いません。

Q. テストがまったく存在しないプロジェクトではどうすればいいですか?

まずテストフレームワークの導入とCI設定から始める必要があります。その上で、最低限「振る舞いを固定するテスト」だけは必ず先に書きましょう。テストなしでのリファクタリングは、Claude Codeを使っていても人間が手作業で行うのと同程度のリスクを伴います。

Q. リファクタリングにどれくらい時間をかけるべきですか?

明確な基準はありませんが、「計画立案にかける時間」と「実装にかける時間」が同程度、あるいは計画の方が長くなることも珍しくありません。特に高リスクと判定された変更では、計画段階を惜しまないことが結果的に手戻りを減らします。

まとめ

  • リファクタリング前に影響範囲を調査し、呼び出し元の数・テストカバレッジ・副作用の3軸でリスクを評価する
  • 計画はPLAN.mdなどのファイルに書き出し、会話の中だけで終わらせない
  • 既存の振る舞いをテストで固定してから内部実装を変える「テスト駆動リファクタリング」を基本動作にする
  • 大規模な変更は「1ステップ=1責務」の原則で分割し、各ステップでスコープを明示的に制限する
  • diffの都度確認・Hookによる機械的チェック・サブエージェントによる第三者レビューを安全弁として組み合わせる
具体的な手順をもっと知りたい場合

テストの有無に応じた進め方や、より詳細なプロンプト例は実践レシピ「レガシーコードを安全にリファクタリングする」も参照してください。このレッスンで学んだ計画・リスク評価の考え方と組み合わせると、実務でより安全にリファクタリングを進められます。

次のステップ

リファクタリングを安全に進めるための土台は、結局のところ「信頼できるテスト」です。次のLesson 3では、テストコードを効率的に生成するための戦略設計を学びます。

ユニット・結合・E2Eの使い分け、カバレッジ目標の設定、CI連携まで、テスト生成を体系的に扱っていきます。