Use Cases (更新: 2026/6/1)

Claude Codeでリファクタリングを安全に自動化する実務手順

Claude Codeでリファクタリングを小さく安全に進める手順。前後比較、テスト、git diff、レビュー観点まで実例で解説。

Claude Codeでリファクタリングを安全に自動化する実務手順

リファクタリング自動化で最初に決めること

Claude Codeでリファクタリングを自動化するとき、いちばん大事なのは「いきなり賢く直してもらうこと」ではありません。最初に決めるべきなのは、変更範囲、成功条件、止める条件です。リファクタリングは、外から見える機能を変えずに、内部の構造を読みやすく、テストしやすく、変更しやすくする作業です。だからこそ、便利なAIに大きな塊を任せるほど、思わぬ仕様変更が混ざる危険も増えます。

この記事では、Claude Codeを「自動で全部やる魔法の人」ではなく、「小さな差分を作って検証まで走る実務パートナー」として扱う手順をまとめます。前提として、Claude Codeの基本的な作業フローは公式のcommon workflowsが参考になります。権限や実行コマンドの制御はsettingsで確認してください。

関連する安全設計は、Claude Codeの権限設定ガイドClaude Codeのコンテキスト管理でも扱っています。この記事は、その実務版として「どの順番で任せると事故りにくいか」に寄せます。

私がMasaとして実際に小さな検証で試した感触では、Claude Codeは「リネーム」「関数抽出」「型の明確化」「テスト追加」のような境界がはっきりした作業では強いです。一方で、「全体をきれいにして」「設計をモダンにして」のような依頼は、差分が広がりやすくレビューコストが跳ねます。まずは安全な順番で進めるのが、結果的にいちばん速いです。

安全な順番は「調査、計画、1差分、検証」

初心者が最初に試すなら、次の順番を固定してください。Claude Codeに編集を許す前に、まず調査と計画だけを依頼します。

段階Claude Codeに任せること人間が見ること
1. 調査対象ファイル、依存、テストの候補を読む変更範囲が広すぎないか
2. 計画3ステップ以内の小さな作業案を出す仕様変更が混ざっていないか
3. 実装1テーマだけ変更する差分が読み切れる量か
4. 検証test、typecheck、lintを実行する失敗の原因が説明されているか
5. レビューgit diffを要約するBefore/Afterが同じ振る舞いか

最初に使うプロンプトはこれで十分です。

このリポジトリで安全にリファクタリングできる候補を調査してください。
まだファイルは編集しないでください。

条件:
- 外部仕様を変えない
- 1回の差分は3ファイル以内
- 既存テストで確認できる箇所を優先
- 変更候補、理由、確認コマンド、想定リスクを表で出す

ここで重要なのは「まだ編集しないでください」と明記することです。Claude Codeはプロジェクトを読んで作業を進められるので、曖昧に頼むと実装まで進むことがあります。特に業務コードでは、調査と実装を分けるだけで事故率がかなり下がります。

作業ブランチも先に切っておきます。

git status --short
git checkout -b refactor/safe-extract-order-total
npm test
npm run typecheck
npm run lint

プロジェクトによってコマンド名は違います。package.jsonを見て、testtypechecklintに相当するコマンドへ置き換えてください。最初のベースラインでテストが落ちている場合は、リファクタリング前から落ちていたのか、変更で壊したのかが分からなくなります。そこを曖昧にしたまま進めると、AIより人間のレビューが苦しくなります。

ユースケース1: 変数名と小さな関数抽出から始める

最初の実例は、最も安全な「名前の改善」と「純粋関数の抽出」です。純粋関数とは、同じ入力なら同じ出力を返し、DB更新やAPI呼び出しのような副作用を持たない関数です。ここはClaude Codeに任せやすい領域です。

// before: src/domain/order.ts
export function calc(o: { items: { p: number; q: number }[]; d?: number }) {
  let t = 0;
  for (const i of o.items) {
    t += i.p * i.q;
  }
  if (o.d) {
    t = t - o.d;
  }
  return Math.max(t, 0);
}

このコードは短いですが、pqdの意味が読み手に伝わりません。Claude Codeには、振る舞いを変えず、まずテストを足してから名前を直すよう頼みます。

src/domain/order.ts の calc 関数を安全にリファクタリングしてください。

条件:
- 先に現在の振る舞いを固定する単体テストを追加
- 外部公開名 calc は今回は変えない
- 変数名と型名を読みやすくする
- 割引後の金額が0未満にならない仕様は維持
- 変更後に npm test -- order を実行

期待するAfterはこのくらいです。

// after: src/domain/order.ts
type OrderLine = {
  price: number;
  quantity: number;
};

type OrderInput = {
  items: OrderLine[];
  discount?: number;
};

export function calc(order: OrderInput): number {
  const subtotal = order.items.reduce(
    (sum, item) => sum + item.price * item.quantity,
    0
  );

  return Math.max(subtotal - (order.discount ?? 0), 0);
}

テスト例はコピペできる形で用意します。

// src/domain/order.test.ts
import { describe, expect, it } from "vitest";
import { calc } from "./order";

describe("calc", () => {
  it("multiplies price and quantity", () => {
    expect(calc({ items: [{ price: 1200, quantity: 2 }] })).toBe(2400);
  });

  it("applies discount without returning a negative total", () => {
    expect(calc({ items: [{ price: 500, quantity: 1 }], discount: 800 })).toBe(0);
  });
});

レビュー時は、まず差分を小さく見ます。

git diff -- src/domain/order.ts src/domain/order.test.ts
npm test -- order
npm run typecheck

この例のレビュー観点は、「計算結果が変わっていないか」「外部公開名を変えていないか」「テストが仕様を説明しているか」の3つです。リファクタリングの目的は賢そうなコードにすることではなく、次の修正者が迷わない形にすることです。

ユースケース2: anyを消すときは境界から型を固める

次に多いのがanyの削減です。anyはTypeScriptの安全装置を外す指定です。全部を一気に消そうとすると広範囲に型エラーが広がるので、APIレスポンス、フォーム入力、設定ファイルの読み込みなど、外部からデータが入る境界から固めます。

// before: src/lib/user-api.ts
export async function fetchUser(id: string): Promise<any> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

export function getDisplayName(user: any): string {
  return user.profile.displayName || user.name;
}

Claude Codeへの依頼は、型定義だけでなく、壊れた入力をどう扱うかまで含めます。

src/lib/user-api.ts の any を減らしてください。

条件:
- APIレスポンス用の型を追加
- profile がない場合も落ちないようにする
- getDisplayName の既存仕様をテストで固定
- fetch のURLや戻り値の意味は変えない
- 変更後に npm test -- user-api と npm run typecheck を実行

Afterはこうです。

// after: src/lib/user-api.ts
export type UserResponse = {
  id: string;
  name: string;
  profile?: {
    displayName?: string;
  };
};

export async function fetchUser(id: string): Promise<UserResponse> {
  const response = await fetch(`/api/users/${id}`);
  return response.json() as Promise<UserResponse>;
}

export function getDisplayName(user: UserResponse): string {
  return user.profile?.displayName ?? user.name;
}

この段階で、as Promise<UserResponse>が本当に十分かはプロジェクト次第です。実行時にも検証したいなら、zodなどのバリデーションを入れる方が安全です。ただし、リファクタリングの1差分でライブラリ追加までやるとレビューが重くなります。最初の差分では型を明確にし、次の差分で実行時バリデーションを足す、という分け方が現実的です。

テストは、正常系だけでなく欠けているデータも入れます。

// src/lib/user-api.test.ts
import { describe, expect, it } from "vitest";
import { getDisplayName, type UserResponse } from "./user-api";

describe("getDisplayName", () => {
  it("uses profile displayName when present", () => {
    const user: UserResponse = {
      id: "u1",
      name: "Masa",
      profile: { displayName: "Masa I." },
    };

    expect(getDisplayName(user)).toBe("Masa I.");
  });

  it("falls back to name when profile is missing", () => {
    expect(getDisplayName({ id: "u2", name: "Guest" })).toBe("Guest");
  });
});

Claude Codeの返答で「型エラーは消えました」と書かれていても、必ず人間側でgit diffを読みます。型のために戻り値を空文字にした、例外を握りつぶした、未定義を無理やりキャストした、という修正は見た目より危険です。

ユースケース3: 巨大関数はテストの足場を作ってから分割する

巨大関数の分割は効果が大きい一方で、事故も起きやすいです。特に注文処理、請求処理、権限判定のように条件分岐が多い関数は、AIが「読みやすくする」過程で分岐順序を変えてしまうことがあります。

// before: src/services/order-service.ts
export async function createOrder(input: CreateOrderInput) {
  if (input.items.length === 0) {
    throw new Error("items required");
  }

  const subtotal = input.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  const shippingFee = subtotal >= 10000 ? 0 : 800;
  const total = subtotal + shippingFee;

  const order = await db.order.create({
    data: {
      userId: input.userId,
      subtotal,
      shippingFee,
      total,
    },
  });

  await mailer.sendOrderCreated(order.id);
  return order;
}

ここでは、DBやメール送信を含む関数をいきなり全部きれいにしません。まず副作用のない計算だけを抜き出します。

src/services/order-service.ts の createOrder を小さくしてください。

今回やること:
- 送料と合計金額の計算だけを pure function として抽出
- DB保存とメール送信の順序は変えない
- 関数名は calculateOrderTotals にする
- 既存テストがなければ calculateOrderTotals の単体テストを追加

今回やらないこと:
- DBスキーマ変更
- エラー文言変更
- mailer の呼び出し条件変更
- ファイル全体のフォーマット変更

Afterの形です。

// after: src/services/order-service.ts
export function calculateOrderTotals(items: OrderItem[]) {
  const subtotal = items.reduce(
    (sum, item) => sum + item.price * item.quantity,
    0
  );
  const shippingFee = subtotal >= 10000 ? 0 : 800;

  return {
    subtotal,
    shippingFee,
    total: subtotal + shippingFee,
  };
}

export async function createOrder(input: CreateOrderInput) {
  if (input.items.length === 0) {
    throw new Error("items required");
  }

  const { subtotal, shippingFee, total } = calculateOrderTotals(input.items);

  const order = await db.order.create({
    data: { userId: input.userId, subtotal, shippingFee, total },
  });

  await mailer.sendOrderCreated(order.id);
  return order;
}

この差分なら、レビューで見るべき場所がはっきりします。計算式、送料無料条件、副作用の順番だけです。

git diff --stat
git diff -- src/services/order-service.ts
git diff -- src/services/order-service.test.ts
npm test -- order-service

もしClaude Codeがついでにファイル全体を整形した場合は、レビューが難しくなります。そのときは次のように戻します。

今回の差分は大きすぎます。
フォーマットだけの変更を戻し、calculateOrderTotals の抽出とテスト追加だけに絞ってください。
外部仕様、エラー文言、DB保存、メール送信の順序は変えないでください。

git diffで見るレビュー観点

Claude Codeのリファクタリングは、最終的にgit diffで判断します。会話の説明がどれだけ自然でも、差分が読めなければ公開やマージは危険です。

git diff --check
git diff --stat
git diff --name-only
git diff --word-diff -- src/domain/order.ts

レビューでは次を確認します。

観点見るポイント
仕様入出力、例外、HTTPステータス、DB更新順序が変わっていないか
差分量1回で読み切れるファイル数か
テスト変更前の振る舞いを固定するテストがあるか
as anyや無理なキャストで黙らせていないか
副作用API呼び出し、メール送信、課金、削除処理の順番が変わっていないか
説明Claude Codeの要約と実際のdiffが一致しているか

レビュー用のプロンプトも固定化しておくと便利です。

このgit diffをレビューしてください。

観点:
- リファクタリングの範囲を超えて仕様変更していないか
- テストで守られていない振る舞いはどこか
- 型を無理に黙らせていないか
- 追加で人間が見るべきファイルはどれか

出力:
- 問題なし
- 要確認
- 修正必須
の3分類で、根拠とファイル名を出してください。

このレビューをClaude Code自身にやらせる場合も、最後は人間が1回は見ます。特に削除、課金、通知、権限のコードは、通ったテストよりも業務影響の方が大きいことがあります。

落とし穴:失敗例と避け方

失敗例1は、広すぎる依頼です。

このサービス層を読みやすくリファクタリングして。

この依頼だと、関数分割、命名変更、例外設計、ファイル移動、フォーマットが一度に混ざります。避けるには、目的を1つに絞ります。

今回は createOrder の送料計算だけを pure function に抽出してください。
それ以外の処理順序、エラー文言、戻り値は変えないでください。

失敗例2は、テストなしで「差分がきれいだからOK」にすることです。見た目が読みやすくなっても、境界条件が変わっていることがあります。割引が0未満にならない、送料が一定金額以上で無料になる、権限がないユーザーは拒否される、こうした仕様はテストに落としてから触ります。

失敗例3は、フォーマッタとリファクタリングを同じ差分に入れることです。PrettierやESLintの自動修正が数百行の差分を作ると、実質的な変更が見えにくくなります。フォーマットだけのPRと、構造変更のPRは分けた方がレビューは速いです。

失敗例4は、Claude Codeの実行権限を広くしすぎることです。最初から任意のコマンド実行を許すのではなく、テスト、型チェック、lint、読み取り系コマンドから始めます。設定を固定する考え方はClaude Codeの権限設定ガイドを参照してください。

毎回使うチェックリスト

実務では、毎回このチェックリストを貼ってから作業すると安定します。

## Refactoring checklist

- [ ] 変更目的は1つだけか
- [ ] 編集前に既存テストを実行したか
- [ ] Before/Afterで外部仕様が同じか
- [ ] 新しいテストまたは既存テストで振る舞いを確認できるか
- [ ] git diff --stat が読み切れる量か
- [ ] git diff --check が通るか
- [ ] any、無理なキャスト、例外握りつぶしが増えていないか
- [ ] DB、メール、課金、削除、権限の順序が変わっていないか

Claude Codeへ渡す最終プロンプトは次の形です。

リファクタリングを1差分だけ実行してください。

対象:
- src/services/order-service.ts
- src/services/order-service.test.ts

成功条件:
- 外部仕様を変えない
- calculateOrderTotals を抽出する
- 既存テストと追加テストが通る
- git diff --stat と実行したコマンドを最後に報告する

禁止:
- DBスキーマ変更
- APIレスポンス変更
- エラー文言変更
- unrelated file の編集

このテンプレートを使うと、Claude Codeの作業が「いい感じに直す」から「成功条件を満たす小さな差分を作る」に変わります。レビューする側も、どこを見ればよいかが明確になります。

Masaの検証メモと次の一手

この記事で紹介した内容を実際に試した結果、いちばん効果があったのは「編集前の調査プロンプト」と「git diffのレビュー固定化」でした。Claude Codeは、対象が狭く、テストコマンドが明確で、やらないことが書かれているほど安定します。逆に、抽象的な改善依頼では差分が広がり、レビューに時間がかかりました。

まずは、名前変更、純粋関数の抽出、any削減のどれか1つから試してください。うまくいったら、Claude Codeのレビュー用チェックリストclaude.mdのベストプラクティスと組み合わせると、チームでも再現しやすくなります。

社内でClaude Codeを安全に使うルールを整えたい場合は、Claude Code研修で、権限設定、レビュー観点、実務プロンプト、運用ルールまで一緒に設計できます。リファクタリング自動化は、速さよりも「壊さず続けられる型」を作るのが先です。そこができると、日々の改善が怖くなくなります。

#Claude Code #リファクタリング #コード品質 #自動化 #TypeScript
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。