CLAUDE LABEN
TEACHERS — AnthropicがClaude for Teachersを公開。米国K-12の認証済み教員に、プレミアム機能と授業向けスキル、全50州の学習指導基準に沿ったカリキュラム連携を無償提供しますADMIN — Claude Enterprise全組織でAdmin APIがベータ提供に。メンバーと招待の操作はベータヘッダー不要、グループとカスタムロールは専用ヘッダーが必要ですM365 — Microsoft 365コネクタに書き込みツールが追加。メールの下書き・送信・整理、カレンダー操作、OneDriveとSharePointのファイル作成・更新まで任せられますMCP — --mcp-configや.mcp.jsonのサーバ単位request_timeout_msが無視され、新規セッションで60秒既定のままタイムアウトする不具合が修正されましたSUBAGENT — --forward-subagent-textとCLAUDE_CODE_FORWARD_SUBAGENT_TEXTが追加。サブエージェントのテキストと思考をstream-json出力に含められますDEADLINE — 7月24日にOpus 4.7のfastモードが削除されます。残り7日、speed: "fast" はエラーになるためOpus 4.8への移行をご確認くださいTEACHERS — AnthropicがClaude for Teachersを公開。米国K-12の認証済み教員に、プレミアム機能と授業向けスキル、全50州の学習指導基準に沿ったカリキュラム連携を無償提供しますADMIN — Claude Enterprise全組織でAdmin APIがベータ提供に。メンバーと招待の操作はベータヘッダー不要、グループとカスタムロールは専用ヘッダーが必要ですM365 — Microsoft 365コネクタに書き込みツールが追加。メールの下書き・送信・整理、カレンダー操作、OneDriveとSharePointのファイル作成・更新まで任せられますMCP — --mcp-configや.mcp.jsonのサーバ単位request_timeout_msが無視され、新規セッションで60秒既定のままタイムアウトする不具合が修正されましたSUBAGENT — --forward-subagent-textとCLAUDE_CODE_FORWARD_SUBAGENT_TEXTが追加。サブエージェントのテキストと思考をstream-json出力に含められますDEADLINE — 7月24日にOpus 4.7のfastモードが削除されます。残り7日、speed: "fast" はエラーになるためOpus 4.8への移行をご確認ください
記事一覧/Claude Code
Claude Code/2026-03-18中級

Claude Code でテスト駆動リファクタリング — レガシーコードを安全に現代化する

Claude Code を使ってレガシーコードにテストを追加しながら安全にリファクタリングする手法を解説。TDD の原則とAIの組み合わせで、品質を落とさず技術的負債を解消する実践的ワークフローを紹介します。

claude-code129TDD4リファクタリング6テスト3レガシーコード

なぜ「テスト駆動リファクタリング」が必要なのか

多くの開発プロジェクトには、何年もかけて積み重なった「レガシーコード」が存在します。動いているけれど理解しにくく、修正するたびにどこかが壊れる——そんなコードに悩んでいる開発者は多いはずです。

Claude Code はこの問題を解決する強力なパートナーになります。単にコードを書き直すのではなく、テストを足場にしながら段階的にリファクタリングするアプローチによって、既存の動作を保証しつつ安全に現代化できます。


ステップ 1: 対象コードの「キャラクタリゼーションテスト」を作る

リファクタリングの第一歩は、現在の挙動を記録するテスト(キャラクタリゼーションテスト) を書くことです。これは「正しい動作」をテストするのではなく、「現在の動作」を固定するものです。

Claude Code へのプロンプト例

以下のレガシー関数を読んで、現在の挙動を網羅するキャラクタリゼーションテストを
Vitest/Jest 形式で書いてください。エッジケース(null、空配列、負の数など)も含めてください。

[対象コードを貼り付ける]

Claude Code は関数の挙動を解析し、様々な入力パターンに対するテストケースを自動生成します。この段階では「正しいかどうか」より「網羅性」が重要です。

実践例:複雑な価格計算関数のテスト化

// リファクタリング前のレガシーコード
function calcPrice(items, user, promo) {
  let total = 0;
  for (let i = 0; i < items.length; i++) {
    let p = items[i].price;
    if (user && user.isPremium) p = p * 0.9;
    if (promo && promo.type === 'percent') p = p * (1 - promo.value / 100);
    if (promo && promo.type === 'fixed') p = p - promo.value;
    if (p < 0) p = 0;
    total += p * items[i].qty;
  }
  if (user && user.isPremium && total > 10000) total = total * 0.95;
  return Math.round(total);
}

Claude Code に上記を渡すと、以下のようなテストセットを生成します:

import { describe, it, expect } from 'vitest';
import { calcPrice } from './pricing';
 
describe('calcPrice', () => {
  const baseItems = [{ price: 1000, qty: 2 }, { price: 500, qty: 1 }];
 
  it('通常ユーザー、プロモなし', () => {
    expect(calcPrice(baseItems, null, null)).toBe(2500);
  });
 
  it('プレミアムユーザーは10%割引', () => {
    expect(calcPrice(baseItems, { isPremium: true }, null)).toBe(2250);
  });
 
  it('パーセント割引プロモ', () => {
    expect(calcPrice(baseItems, null, { type: 'percent', value: 20 })).toBe(2000);
  });
 
  it('固定額割引プロモ', () => {
    expect(calcPrice(baseItems, null, { type: 'fixed', value: 300 })).toBe(1900);
  });
 
  it('価格がマイナスになる場合は0に丸める', () => {
    const cheapItems = [{ price: 100, qty: 1 }];
    expect(calcPrice(cheapItems, null, { type: 'fixed', value: 500 })).toBe(0);
  });
 
  it('プレミアム & 合計1万超で追加5%割引', () => {
    const bigItems = [{ price: 5000, qty: 3 }];
    // プレミアム10%後: 13500 > 10000 → さらに5%割引
    expect(calcPrice(bigItems, { isPremium: true }, null)).toBe(12825);
  });
 
  it('空の配列は0を返す', () => {
    expect(calcPrice([], null, null)).toBe(0);
  });
});

このテストが全部パスすることを確認してから、次のステップへ進みます。


ステップ 2: Claude Code にリファクタリング計画を立てさせる

テストが揃ったら、Claude Code にリファクタリングの方向性を相談します。

上記のcalcPrice関数をリファクタリングしたいです。
以下の制約を守って改善案を提示してください:
- 既存のテストが全てパスし続けること
- 単一責任の原則に従ってロジックを分割すること
- TypeScript に移行すること
- 各変換ステップを関数として分離すること

Claude Code は段階的なリファクタリング計画を提示します:

// ステップ1: 型定義
interface Item {
  price: number;
  qty: number;
}
 
interface User {
  isPremium: boolean;
}
 
interface Promo {
  type: 'percent' | 'fixed';
  value: number;
}
 
// ステップ2: 各変換を純粋関数に分離
function applyPremiumDiscount(price: number, user: User | null): number {
  return user?.isPremium ? price * 0.9 : price;
}
 
function applyPromo(price: number, promo: Promo | null): number {
  if (!promo) return price;
  if (promo.type === 'percent') return price * (1 - promo.value / 100);
  if (promo.type === 'fixed') return Math.max(0, price - promo.value);
  return price;
}
 
function applyBulkDiscount(total: number, user: User | null): number {
  return user?.isPremium && total > 10000 ? total * 0.95 : total;
}
 
function calcItemTotal(item: Item, user: User | null, promo: Promo | null): number {
  let price = applyPremiumDiscount(item.price, user);
  price = applyPromo(price, promo);
  return price * item.qty;
}
 
// ステップ3: メイン関数は協調のみ担当
export function calcPrice(
  items: Item[],
  user: User | null,
  promo: Promo | null
): number {
  const subtotal = items.reduce(
    (sum, item) => sum + calcItemTotal(item, user, promo),
    0
  );
  return Math.round(applyBulkDiscount(subtotal, user));
}

重要なのは、リファクタリング後も全テストがパスすることです。Claude Code は「テストをガイドに使い、コードを変える」というアプローチを徹底します。


ステップ 3: CLAUDE.md で「リファクタリングルール」を定義する

プロジェクトルートに CLAUDE.md を置くことで、Claude Code に一貫したリファクタリング方針を守らせることができます:

# CLAUDE.md — リファクタリングガイドライン
 
## 必須ルール
- リファクタリング前に必ずキャラクタリゼーションテストを作成する
- 既存テストが全てパスすることを確認してから次のステップへ進む
- 1つのコミットで変更するファイルは3つ以内に限定する
- TypeScript strict モードを使用する
 
## 分割の原則
- 1関数は1つの責務のみ
- 副作用のある処理とピュアな計算を分離する
- データ変換はパイプラインとして表現する
 
## 禁止事項
- テストなしでのリファクタリング
- 一度に複数の変更(動作変更 + 構造変更の同時実施)

ステップ 4: 段階的コミット戦略

Claude Code と協力してリファクタリングを進める際は、小さなコミットを積み重ねる戦略が効果的です。

推奨コミット粒度

# Bad: 一度に全部変える
git commit -m "refactor: rewrite pricing module"
 
# Good: 段階的に積み上げる
git commit -m "test: add characterization tests for calcPrice"
git commit -m "types: add TypeScript interfaces for pricing domain"
git commit -m "refactor: extract applyPremiumDiscount pure function"
git commit -m "refactor: extract applyPromo pure function"
git commit -m "refactor: extract applyBulkDiscount pure function"
git commit -m "refactor: simplify calcPrice to use extracted functions"

各コミット後に npm test を実行し、全テストのパスを確認します。Claude Code にこのフローを指示するプロンプト:

今からリファクタリングを段階的に進めます。
各ステップ後に必ずテストを実行し、パスを確認してからコミットしてください。
一度に変えるのは1つの関数だけにしてください。

ステップ 5: 依存関係の注入でテスタビリティを高める

レガシーコードの多くは、外部依存(DB、API、時刻など)がハードコードされていて、テストが難しいです。Claude Code はこの問題を解決するリファクタリングも得意とします。

以下の関数は Date.now() と外部APIを直接呼んでいます。
依存関係の注入パターンを使って、テスタブルに改善してください。

[対象コードを貼り付ける]

Claude Code は外部依存を引数として受け取る形に変換し、テストでモックが使えるようにします:

// Before(テスト困難)
async function generateReport(userId: string) {
  const user = await db.users.findById(userId);
  const now = Date.now();
  return { user, generatedAt: now };
}
 
// After(依存注入でテスタブル)
interface ReportDeps {
  findUser: (id: string) => Promise<User>;
  getNow: () => number;
}
 
async function generateReport(
  userId: string,
  deps: ReportDeps = {
    findUser: (id) => db.users.findById(id),
    getNow: () => Date.now(),
  }
) {
  const user = await deps.findUser(userId);
  return { user, generatedAt: deps.getNow() };
}
 
// テストでは依存をモックに差し替え
it('レポートを生成する', async () => {
  const mockDeps = {
    findUser: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }),
    getNow: vi.fn().mockReturnValue(1700000000000),
  };
  const result = await generateReport('1', mockDeps);
  expect(result.generatedAt).toBe(1700000000000);
});

Claude Code の「説明モード」を活用する

リファクタリング中に既存コードの意図が不明な場合、Claude Code の解説機能が役立ちます:

このコードが何をしているか、なぜこう書かれているか推測して説明してください。
特に、条件分岐のビジネスロジック的な意味を重点的に説明してください。

Claude Code はコードから意図を読み解き、ビジネスルールとして文章化します。これをドキュメントやコメントに転用することで、次世代のチームメンバーへの知識移転にもなります。


全体を振り返って:Claude Code を使ったリファクタリングの流れ

  1. キャラクタリゼーションテストの作成 — 現状の挙動を記録
  2. リファクタリング計画の立案 — Claude Code に方向性を相談
  3. CLAUDE.md でルールを定義 — 一貫した基準を設ける
  4. 段階的コミット — 小さな変更を積み重ねる
  5. 依存関係の注入 — テスタビリティを向上させる

重要なのは「一気に書き直さない」こと。Claude Code をペアプログラマーとして使いながら、テストという安全網の中で少しずつ改善するアプローチが、長期的な品質向上につながります。

テスト駆動リファクタリングは一日では完結しませんが、Claude Code があれば毎日少しずつ技術的負債を解消し続けることができます。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

Claude Code2026-07-17
テーブルが「… 2,847 more rows」で切れていた朝 — 描画上限とトークン計上を切り離して、ツール出力を設計し直す
Claude Code 2.1.209 で 200 行超の markdown テーブルは先頭 200 行+残り件数の表示になりました。省略されるのは描画だけで、モデルは全行を受け取ります。この差を測り、ツール出力を集約型へ組み替える設計をまとめます。
Claude Code2026-07-15
無人実行で auto モードの確認要求を止めずに捌く — permission-prompt-tool で deny-by-default に応える
auto モードの事前確認は、手元で作業しているときは頼もしい味方ですが、深夜の無人実行では「確認待ち」で朝まで止まる原因になります。permission-prompt-tool で確認要求を受け止め、deny-by-default で自動判定して台帳に残す構成を、動くコードつきでまとめました。
Claude Code2026-07-14
push先がいつの間にか一つ増えていた — /commit-push-pr が origin 以外のリモートへも押し始めた変更に、許可リストで蓋をする
7月14日の更新で /commit-push-pr が origin に加えて設定済みの push リモートへも自動で押すようになりました。便利な一方、ミラーやバックアップ用の第二リモートを持つ環境では、意図しない push が静かに増えます。どのリモートへ押し得るかを棚卸しし、許可リスト外の push を pre-push フックで止め、無人実行でも安全に回す設計を実装込みで整理しました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →