Claude Codeでリファクタリングするときの注意点
はじめに
リファクタリング(機能を変えずにコード構造を改善する作業)は、AI に任せるのが難しいタスクです。本記事では、Claude Code を使ったリファクタリングを安全に進める方法を、段階的なアプローチで解説します。
Claude Codeで失敗しやすい指示で「複数の関心事を混ぜない」ことを説明しましたが、リファクタリングはその最たる例です。
リファクタリングが危険な理由
問題1:「リファクタリング」は定義が曖昧
❌ 曖昧な指示:
「このコードをリファクタリングしてください」
AI が考える「リファクタリング」:
- 変数名を改善?
- 関数を分割?
- デザインパターンを適用?
- パフォーマンスを最適化?
- 依存関係を削除?
全て実行されてしまう可能性
問題2:既存テストを信頼できない
❌ 危険な流れ:
既存テストは古いコード用に書かれている
リファクタリング後、
既存テストはそのまま pass
↓
「テストが通ったからOK」と思う
↓
実は仕様を誤解していて、
実装が変わってしまっている
安全なリファクタリングの 5 ステップ
ステップ1:分析フェーズ
指示:「以下を分析して報告してください(実装はしないでください)
対象コード: src/services/auth.ts の login() 関数
分析内容:
1. 現在の構造と問題点
- 関数の長さ(行数)
- 分岐の複雑さ(cyclomatic complexity)
- 重複コード
- 読みにくい部分
2. 既存テスト
- tests/services/auth.test.ts の既存テストケース数
- カバーしている範囲
- edge case の coverage
3. リファクタリング候補
- 分割可能な部分関数は何か
- 既存テストで検証できるか
- 各段階で git commit できるか
4. リスク評価
- リファクタリング中に仕様が変わる可能性
- エッジケースを見落とす可能性
- 並行アクセス時の動作変化
」
ステップ2:計画フェーズ
分析結果を基に、段階的な計画を出させます。
指示:「ステップ1 の分析を基に、以下の計画を提示してください
リファクタリング計画:
【フェーズ1】(最初のみ実装)
- 抽出する関数: validateCredentials()
- 実装内容: login() から 「ユーザー存在確認」部分を抽出
- 既存テストへの影響: なし(login() の動作は変わらない)
【フェーズ2】(フェーズ1 承認後)
- 抽出する関数: hashPassword()
- 実装内容: login() から 「パスワードハッシング」部分を抽出
- 依存関係: validateCredentials() に依存しない
【段階ごとの git commit】
- commit 1: validateCredentials() を抽出+テスト確認
- commit 2: hashPassword() を抽出+テスト確認
【注意点】
- 各段階で既存テストは全て pass する
- 新しいユーティリティ関数用の新規テストは不要(login テストで検証)
」
ステップ3:最小単位で実装
計画が承認されたら、最初のフェーズだけ実装させます。
指示:「フェーズ1 のみ実装してください
実装内容:
- src/services/auth.ts の login() から validateCredentials() を抽出
- 関数署名: const validateCredentials = (user, password) => boolean
- 既存テスト tests/services/auth.test.ts は全て pass する
- 新しい関数用の新規テストは作らない(login テストで検証)
完了後:
- git diff を実行して変更内容を確認
- 差分サイズが 30 行以内か確認(大きすぎたら計画を修正)
- npm test で全テスト pass を確認
」
ステップ4:差分レビュー
実装完了後、必ず git diff でレビューします。
確認項目:
✅ 差分の行数
- 30〜50 行程度が目安
- 100 行以上は「細かすぎるフェーズ分割」
✅ 既存テストの動作
- npm test で全て pass
- 失敗がある場合は計画段階で問題があった
✅ 関数の新規追加か既存変更か
- 新規追加は OK(テスト対象外)
- 既存関数の動作が変わっていないか
❌ 予期しない変更
- 他のファイルが編集されていないか
- 別の関数の構造が変わっていないか
ステップ5:次フェーズへ
フェーズ1 が完了したら、フェーズ2 を実装。
指示:「フェーズ2 を実装してください
実装内容:
src/services/auth.ts の login() から hashPassword() を抽出
注意:
- フェーズ1(validateCredentials)の抽出は済んでいる
- フェーズ1 の関数を使用する場合も問題ない
- 既存テストは全て pass する
- 差分が 30 行以内か確認
」
よくある失敗パターン
パターン1:一度に複数の改善を同時実施
❌ 失敗例:
「login() を以下のように改善してください:
1. validateCredentials() を抽出
2. hashPassword() を抽出
3. ロギング追加
4. エラーメッセージを国際化対応
」
リスク:
- 一度に 4 つの変更が起きて追跡不可能
- テスト失敗の原因が判らない
パターン2:「読みやすく」という曖昧な指示
❌ 失敗例:
「このコードを読みやすくリファクタリングしてください」
AI が思う「読みやすさ」:
- 変数名の変更(仕様不明確で間違える可能性)
- コメント追加
- 関数分割
- etc...
何が本当に「読みづらい」のか不明確
パターン3:新機能追加とリファクタリングの混合
❌ 失敗例:
「login() をリファクタリングして、
同時に OTP 機能も追加してください」
リスク:
- 変更と新機能の区別がつかない
- テスト失敗原因の特定が困難
- rollback が複雑
✅ 改善方法
新機能追加とリファクタリングは分離:
【ステップ1】リファクタリングだけ
(関数分割、変数名改善など)
【ステップ2】リファクタリング完了後、新機能追加
(OTP 機能を追加)
リファクタリングの段階的チェックリスト
実装前
- リファクタリング対象を明確に指定(ファイル、関数名)
- 「何が問題か」を具体的に説明
- 段階的な計画を立てる
- 各段階で既存テスト全 pass を確認するよう指示
実装中
- 1 フェーズごとに実装
- 差分が 30〜50 行程度(大きすぎないか)
- 既存テスト全 pass か確認
- 予期しない変更がないか確認
実装後
- git diff で内容をレビュー
- npm test で全テスト pass
- commit ログが意図した内容か確認
- 次フェーズへ進むか判断
まとめ
リファクタリングを安全に進めるキーポイント:
分割:
- 大きなリファクタリングを複数フェーズに分割
- 各フェーズは 30〜50 行程度に限定
- フェーズ間で人間の確認を挟む
検証:
- 各フェーズで既存テスト全 pass を確認
- git diff でレビュー
- 予期しない変更がないか確認
段階性:
- 各フェーズは独立した git commit
- rollback が簡単
- 何か起きたら前のフェーズまで戻りやすい