はじめに

技術的負債の返済は、多くの開発チームにとって終わりなき課題です。古い構文、非推奨API、時代遅れのデザインパターン。これらのレガシーコードを安全に近代化するには、コードベース全体を理解し、影響範囲を正確に把握する必要があります。

Claude Codeは、公式サイトで「Refactor code」を主要なユースケースの一つとして挙げています。コードベース全体を理解するエージェント型アプローチにより、単一ファイルの書き換えにとどまらない、プロジェクト横断的なリファクタリングを可能にします。

本記事では、Claude Codeを使ってレガシーコードを安全にリファクタリングする実践的なテクニックを解説します。この記事を読むことで、以下のことができるようになります。

  • 非推奨APIを検出し、推奨される代替APIへ移行する
  • ES2024など最新のJavaScript/TypeScript仕様にコードを更新する
  • 適切なデザインパターンを適用してコード品質を向上させる
  • パフォーマンスのボトルネックを特定し、最適化する
  • 既存のテストを維持しながら安全にリファクタリングを進める

実行環境

  • オペレーティングシステム: macOS 10.15以上、Ubuntu 20.04以上/Debian 10以上、Windows 10以上(WSL 1/2またはGit for Windows)
  • ハードウェア: 4GB以上のRAM
  • Node.js 18以上(npmインストールの場合のみ必要)
  • インターネット接続(認証およびAI処理に必要)
  • シェル環境: Bash、Zsh、またはFish推奨

前提条件

  • コマンドライン操作の基礎知識
  • Gitの基本操作(clone、commit、push等)
  • プログラミングの基礎知識(言語は問わない)
  • Claude.aiまたはAnthropic Consoleアカウント
  • Claude Codeのインストールと認証が完了していること

リファクタリングの基本戦略

Claude Codeでリファクタリングを行う際の基本的なワークフローを確認しましょう。

flowchart TD
    A[リファクタリング対象の特定] --> B[Plan Modeで影響範囲を分析]
    B --> C[テストの存在確認]
    C --> D{テストあり?}
    D -->|Yes| E[リファクタリング実行]
    D -->|No| F[テストを先に作成]
    F --> E
    E --> G[テスト実行で動作確認]
    G --> H{テスト成功?}
    H -->|Yes| I[コードレビュー]
    H -->|No| J[修正を依頼]
    J --> G
    I --> K[コミット]

安全なリファクタリングの鍵は、変更前後で動作が変わらないことを保証することです。Claude Codeは、既存のテストを認識し、それを維持しながらコードを改善できます。

非推奨APIの検出と更新

レガシーコードでよく見られる問題の一つが、非推奨(deprecated)APIの使用です。Claude Codeは、コードベース全体をスキャンして非推奨APIを検出し、推奨される代替への移行を支援します。

非推奨APIの検出を依頼する

プロジェクト全体の非推奨API使用箇所を洗い出すには、以下のように依頼します。

1
2
> このプロジェクトで使用されている非推奨APIを検出して。
> Node.js、React、および使用しているライブラリの非推奨機能も含めてリストアップして。

Claude Codeは、以下のような分析結果を提供します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
⏺ 非推奨API使用箇所の分析結果:

1. Node.js
   - url.parse() → 推奨: new URL()
     - src/utils/urlHelper.ts:15
     - src/services/apiClient.ts:42

2. React
   - componentWillMount → 推奨: useEffect
     - src/components/UserProfile.tsx:23
   - findDOMNode → 推奨: ref
     - src/components/Modal.tsx:45

3. moment.js (メンテナンスモード)
   - 全プロジェクトで使用 → 推奨: date-fns または dayjs
     - 使用箇所: 47ファイル

4. lodash (一部メソッド)
   - _.pluck → 推奨: _.map
     - src/utils/dataTransform.ts:12

段階的な移行計画の立案

非推奨APIが多数見つかった場合、段階的な移行計画を立てることが重要です。

1
2
3
> 検出された非推奨APIの移行計画を立てて。
> 影響範囲が小さいものから順に、マイルストーンを設定して。
> 各移行のリスクと所要時間の見積もりも含めて。

具体的な移行の実行

Plan Modeで計画を確認した後、Accept Modeに切り替えて実際の移行を行います。

1
2
> url.parse()をすべてnew URL()に置き換えて。
> 各ファイルでの変更内容を確認させて。

Claude Codeは、単純な置換ではなく、コンテキストを理解した上で適切な変換を行います。

変更前(レガシーコード):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const url = require('url');

function parseApiUrl(urlString) {
  const parsed = url.parse(urlString, true);
  return {
    host: parsed.host,
    pathname: parsed.pathname,
    query: parsed.query
  };
}

変更後(近代化されたコード):

1
2
3
4
5
6
7
8
function parseApiUrl(urlString) {
  const parsed = new URL(urlString);
  return {
    host: parsed.host,
    pathname: parsed.pathname,
    query: Object.fromEntries(parsed.searchParams)
  };
}

ライブラリ移行の実践

moment.jsからdate-fnsへの移行のように、ライブラリ全体を置き換える場合は、より慎重なアプローチが必要です。

1
2
3
> moment.jsをdate-fnsに移行したい。
> まず、現在使用しているmomentの機能と、date-fnsでの対応する関数を
> マッピング表として作成して。
moment.js date-fns 補足
moment() new Date() 現在日時
moment(date).format('YYYY-MM-DD') format(date, 'yyyy-MM-dd') フォーマット文字列が異なる
moment(date).add(1, 'days') addDays(date, 1) 関数ベース
moment(date).isBefore(other) isBefore(date, other) 同様のAPI
moment.duration(ms).humanize() formatDistance(0, ms) 表現が若干異なる

マッピングを確認した後、段階的に移行を進めます。

1
2
> マッピング表に基づいて、src/utils/dateHelper.ts のmomentをdate-fnsに移行して。
> 既存のテストが通ることを確認しながら進めて。

ES2024など最新仕様への移行

JavaScriptとTypeScriptは急速に進化しており、新しい言語機能を活用することでコードの可読性と保守性が向上します。

現在のコードベースの言語機能分析

1
2
3
> このプロジェクトのJavaScriptコードを分析して、
> ES2020以前の古い構文で書かれている箇所を特定して。
> ES2024までの新機能で改善できる箇所もリストアップして。

ES2024への移行例

Claude Codeは、以下のような最新仕様への移行を支援します。

Optional Chaining(オプショナルチェーン)への移行:

1
2
3
> 以下のパターンをOptional Chainingに置き換えて:
> - obj && obj.prop && obj.prop.value
> - user && user.address && user.address.city

変更前:

1
2
3
4
5
6
function getUserCity(user) {
  if (user && user.address && user.address.city) {
    return user.address.city;
  }
  return 'Unknown';
}

変更後:

1
2
3
function getUserCity(user) {
  return user?.address?.city ?? 'Unknown';
}

Array.prototype.at()の活用:

1
> 配列の最後の要素にアクセスするコードを、Array.at()を使った形式に更新して。

変更前:

1
2
const lastItem = items[items.length - 1];
const secondToLast = items[items.length - 2];

変更後:

1
2
const lastItem = items.at(-1);
const secondToLast = items.at(-2);

Object.groupBy()(ES2024)の活用:

1
> reduce()でグループ化しているコードを、Object.groupBy()に置き換えて。

変更前:

1
2
3
4
5
6
7
8
const groupedByStatus = orders.reduce((acc, order) => {
  const key = order.status;
  if (!acc[key]) {
    acc[key] = [];
  }
  acc[key].push(order);
  return acc;
}, {});

変更後:

1
const groupedByStatus = Object.groupBy(orders, order => order.status);

async/awaitへの移行

Promiseチェーンをasync/awaitに書き換えることで、非同期コードの可読性が大幅に向上します。

1
2
3
> src/services/ 以下のPromiseチェーン(.then().catch())を
> async/await構文に書き換えて。
> エラーハンドリングはtry-catchで適切に行うこと。

変更前:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function fetchUserWithOrders(userId) {
  return fetchUser(userId)
    .then(user => {
      return fetchOrders(user.id)
        .then(orders => {
          return { user, orders };
        });
    })
    .catch(error => {
      console.error('Error:', error);
      throw error;
    });
}

変更後:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
async function fetchUserWithOrders(userId) {
  try {
    const user = await fetchUser(userId);
    const orders = await fetchOrders(user.id);
    return { user, orders };
  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
}

TypeScript型システムの改善

TypeScriptプロジェクトでは、型定義の改善もリファクタリングの重要な要素です。

1
2
3
> このプロジェクトで使用されているany型を検出し、
> 適切な型定義に置き換える提案をして。
> 優先度の高い順(パブリックAPI、共通ユーティリティ等)にリストアップして。

変更前:

1
2
3
4
5
6
7
function processData(data: any): any {
  const result: any = {};
  for (const key in data) {
    result[key] = data[key].toString();
  }
  return result;
}

変更後:

1
2
3
4
5
6
7
8
9
function processData<T extends Record<string, { toString(): string }>>(
  data: T
): Record<keyof T, string> {
  const result = {} as Record<keyof T, string>;
  for (const key in data) {
    result[key] = data[key].toString();
  }
  return result;
}

デザインパターンの適用

レガシーコードは、適切なデザインパターンが適用されていないことが多くあります。Claude Codeは、コードの構造を分析し、適用すべきパターンを提案できます.

パターン適用候補の特定

1
2
3
> このコードベースを分析して、デザインパターンを適用することで
> 改善できる箇所を特定して。
> 各箇所について、推奨するパターンと適用による効果を説明して。

Claude Codeは、以下のような分析結果を提供します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
⏺ デザインパターン適用候補の分析:

1. Strategy Pattern 推奨
   - 箇所: src/services/PaymentService.ts
   - 現状: 支払い方法ごとにif-elseが連なっている
   - 効果: 新しい支払い方法の追加が容易になる

2. Factory Pattern 推奨
   - 箇所: src/utils/notificationBuilder.ts
   - 現状: 通知タイプごとにswitch文で分岐
   - 効果: 通知タイプの追加・変更が疎結合になる

3. Observer Pattern 推奨
   - 箇所: src/state/orderState.ts
   - 現状: 状態変更時にコールバックをハードコード
   - 効果: 状態監視の追加・削除が柔軟になる

Strategy Patternの適用例

if-elseの連鎖をStrategy Patternで置き換える例を見てみましょう。

1
2
> PaymentServiceにStrategy Patternを適用して。
> 支払い方法ごとのロジックを個別のストラテジークラスに分離して。

変更前:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class PaymentService {
  processPayment(method: string, amount: number, details: PaymentDetails) {
    if (method === 'credit_card') {
      // クレジットカード処理のロジック(50行)
      const card = details.cardNumber;
      // ...
    } else if (method === 'bank_transfer') {
      // 銀行振込処理のロジック(40行)
      const bankCode = details.bankCode;
      // ...
    } else if (method === 'paypal') {
      // PayPal処理のロジック(35行)
      const paypalEmail = details.email;
      // ...
    } else {
      throw new Error('Unsupported payment method');
    }
  }
}

変更後:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 支払い戦略のインターフェース
interface PaymentStrategy {
  process(amount: number, details: PaymentDetails): Promise<PaymentResult>;
}

// クレジットカード戦略
class CreditCardPaymentStrategy implements PaymentStrategy {
  async process(amount: number, details: PaymentDetails): Promise<PaymentResult> {
    const card = details.cardNumber;
    // クレジットカード固有の処理
    return { success: true, transactionId: generateId() };
  }
}

// 銀行振込戦略
class BankTransferPaymentStrategy implements PaymentStrategy {
  async process(amount: number, details: PaymentDetails): Promise<PaymentResult> {
    const bankCode = details.bankCode;
    // 銀行振込固有の処理
    return { success: true, transactionId: generateId() };
  }
}

// PayPal戦略
class PayPalPaymentStrategy implements PaymentStrategy {
  async process(amount: number, details: PaymentDetails): Promise<PaymentResult> {
    const paypalEmail = details.email;
    // PayPal固有の処理
    return { success: true, transactionId: generateId() };
  }
}

// 戦略を管理するコンテキスト
class PaymentService {
  private strategies: Map<string, PaymentStrategy>;

  constructor() {
    this.strategies = new Map([
      ['credit_card', new CreditCardPaymentStrategy()],
      ['bank_transfer', new BankTransferPaymentStrategy()],
      ['paypal', new PayPalPaymentStrategy()],
    ]);
  }

  async processPayment(
    method: string,
    amount: number,
    details: PaymentDetails
  ): Promise<PaymentResult> {
    const strategy = this.strategies.get(method);
    if (!strategy) {
      throw new Error(`Unsupported payment method: ${method}`);
    }
    return strategy.process(amount, details);
  }

  // 新しい支払い方法の追加が容易
  registerStrategy(method: string, strategy: PaymentStrategy): void {
    this.strategies.set(method, strategy);
  }
}

Dependency Injection(DI)の導入

テスタビリティを向上させるために、依存性注入パターンを導入することも重要です。

1
2
3
> UserServiceクラスにDependency Injectionを適用して。
> 現在ハードコードされているEmailServiceとUserRepositoryを
> コンストラクタインジェクションに変更して。

変更前:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class UserService {
  private emailService = new EmailService();
  private userRepository = new UserRepository();

  async createUser(userData: CreateUserDto): Promise<User> {
    const user = await this.userRepository.save(userData);
    await this.emailService.sendWelcomeEmail(user.email);
    return user;
  }
}

変更後:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
interface IEmailService {
  sendWelcomeEmail(email: string): Promise<void>;
}

interface IUserRepository {
  save(userData: CreateUserDto): Promise<User>;
}

class UserService {
  constructor(
    private readonly emailService: IEmailService,
    private readonly userRepository: IUserRepository
  ) {}

  async createUser(userData: CreateUserDto): Promise<User> {
    const user = await this.userRepository.save(userData);
    await this.emailService.sendWelcomeEmail(user.email);
    return user;
  }
}

// 使用時
const userService = new UserService(
  new EmailService(),
  new UserRepository()
);

// テスト時(モックを注入)
const mockEmailService: IEmailService = {
  sendWelcomeEmail: jest.fn(),
};
const mockUserRepository: IUserRepository = {
  save: jest.fn().mockResolvedValue({ id: '1', email: 'test@example.com' }),
};
const testUserService = new UserService(mockEmailService, mockUserRepository);

パフォーマンス改善提案

Claude Codeは、コードのパフォーマンス問題を特定し、改善案を提案できます。

パフォーマンスボトルネックの分析

1
2
3
4
5
6
7
> このプロジェクトのパフォーマンス改善ポイントを分析して。
> 以下の観点でチェックして:
> - N+1クエリ問題
> - 不要な再レンダリング(React)
> - メモリリーク
> - 非効率なアルゴリズム
> - 不要なネットワークリクエスト

N+1クエリ問題の解決

データベースアクセスでよく見られるN+1問題を解決します。

1
2
> getOrdersWithDetails関数にN+1クエリ問題がある。
> JOINまたはバッチクエリを使って最適化して。

変更前(N+1問題あり):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
async function getOrdersWithDetails(userId: string): Promise<OrderWithDetails[]> {
  const orders = await orderRepository.findByUserId(userId);
  
  // N+1問題: 注文ごとにクエリが発行される
  const ordersWithDetails = await Promise.all(
    orders.map(async (order) => {
      const items = await orderItemRepository.findByOrderId(order.id);
      const shipping = await shippingRepository.findByOrderId(order.id);
      return { ...order, items, shipping };
    })
  );
  
  return ordersWithDetails;
}

変更後(最適化済み):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
async function getOrdersWithDetails(userId: string): Promise<OrderWithDetails[]> {
  const orders = await orderRepository.findByUserId(userId);
  
  if (orders.length === 0) {
    return [];
  }
  
  const orderIds = orders.map(o => o.id);
  
  // バッチクエリで一括取得
  const [allItems, allShippings] = await Promise.all([
    orderItemRepository.findByOrderIds(orderIds),
    shippingRepository.findByOrderIds(orderIds),
  ]);
  
  // メモリ上でマッピング
  const itemsByOrderId = groupBy(allItems, 'orderId');
  const shippingsByOrderId = keyBy(allShippings, 'orderId');
  
  return orders.map(order => ({
    ...order,
    items: itemsByOrderId[order.id] || [],
    shipping: shippingsByOrderId[order.id] || null,
  }));
}

Reactコンポーネントの最適化

1
2
> ProductListコンポーネントの不要な再レンダリングを最適化して。
> React.memo、useMemo、useCallbackを適切に使用して。

変更前:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function ProductList({ products, onSelect, filterOptions }) {
  // 毎回フィルタリングが実行される
  const filteredProducts = products.filter(p => 
    p.category === filterOptions.category &&
    p.price >= filterOptions.minPrice &&
    p.price <= filterOptions.maxPrice
  );

  // 毎回新しい関数が作成される
  const handleSelect = (product) => {
    onSelect(product);
  };

  return (
    <div>
      {filteredProducts.map(product => (
        <ProductCard
          key={product.id}
          product={product}
          onSelect={handleSelect}
        />
      ))}
    </div>
  );
}

変更後:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const ProductList = React.memo(function ProductList({
  products,
  onSelect,
  filterOptions
}: ProductListProps) {
  // フィルタリング結果をメモ化
  const filteredProducts = useMemo(() => 
    products.filter(p => 
      p.category === filterOptions.category &&
      p.price >= filterOptions.minPrice &&
      p.price <= filterOptions.maxPrice
    ),
    [products, filterOptions.category, filterOptions.minPrice, filterOptions.maxPrice]
  );

  // コールバック関数をメモ化
  const handleSelect = useCallback((product: Product) => {
    onSelect(product);
  }, [onSelect]);

  return (
    <div>
      {filteredProducts.map(product => (
        <ProductCard
          key={product.id}
          product={product}
          onSelect={handleSelect}
        />
      ))}
    </div>
  );
});

// 子コンポーネントもメモ化
const ProductCard = React.memo(function ProductCard({
  product,
  onSelect
}: ProductCardProps) {
  return (
    <div onClick={() => onSelect(product)}>
      {product.name} - {product.price}
    </div>
  );
});

アルゴリズムの最適化

1
2
> findDuplicates関数がO(n²)の計算量になっている。
> Set を使ってO(n)に最適化して。

変更前(O(n²)):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function findDuplicates(items: string[]): string[] {
  const duplicates: string[] = [];
  for (let i = 0; i < items.length; i++) {
    for (let j = i + 1; j < items.length; j++) {
      if (items[i] === items[j] && !duplicates.includes(items[i])) {
        duplicates.push(items[i]);
      }
    }
  }
  return duplicates;
}

変更後(O(n)):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function findDuplicates(items: string[]): string[] {
  const seen = new Set<string>();
  const duplicates = new Set<string>();
  
  for (const item of items) {
    if (seen.has(item)) {
      duplicates.add(item);
    } else {
      seen.add(item);
    }
  }
  
  return Array.from(duplicates);
}

テストを維持しながらのリファクタリング

リファクタリングの成功は、既存の動作を壊さないことで測られます。Claude Codeは、テストを意識したリファクタリングを支援します。

テストカバレッジの確認

リファクタリング前に、テストの状態を確認します。

1
2
> リファクタリング対象の PaymentService に対するテストの状態を確認して。
> カバレッジが不足している場合は、先にテストを追加する必要がある。

テストファーストなリファクタリング

テストが不足している場合、リファクタリング前にテストを追加します。

1
2
3
4
5
6
> PaymentServiceの現在の動作を保証するテストを作成して。
> 以下のケースをカバーすること:
> - 正常な支払い処理(各支払い方法)
> - 不正な支払い方法の指定
> - 金額が0以下の場合
> - 外部API障害時のエラーハンドリング

リファクタリング実行とテスト確認

テストが揃ったら、リファクタリングを実行し、テストで検証します。

1
2
3
> PaymentServiceにStrategy Patternを適用して。
> 各変更の後、既存のテストを実行して動作確認をして。
> テストが失敗した場合は、原因を特定して修正して。

Claude Codeは、変更を加えるたびにテストを実行し、リグレッションがないことを確認します。

sequenceDiagram
    participant User as ユーザー
    participant Claude as Claude Code
    participant Code as ソースコード
    participant Test as テストスイート

    User->>Claude: Strategy Pattern適用を依頼
    Claude->>Code: インターフェース定義を追加
    Claude->>Test: テスト実行
    Test-->>Claude: 全テスト成功
    Claude->>Code: CreditCardStrategy実装
    Claude->>Test: テスト実行
    Test-->>Claude: 全テスト成功
    Claude->>Code: BankTransferStrategy実装
    Claude->>Test: テスト実行
    Test-->>Claude: 1件失敗
    Claude->>Code: 失敗原因を修正
    Claude->>Test: テスト実行
    Test-->>Claude: 全テスト成功
    Claude-->>User: リファクタリング完了

スナップショットテストの更新

UIコンポーネントのリファクタリングでは、スナップショットテストの更新が必要になることがあります。

1
2
3
> ProductCardコンポーネントをリファクタリングした。
> スナップショットテストが失敗しているが、変更は意図したものである。
> スナップショットを更新して、変更内容が適切か確認して。

大規模リファクタリングの進め方

大規模なリファクタリングでは、段階的なアプローチが重要です。

ブランチ戦略とマイルストーン設定

1
2
3
4
5
6
7
> moment.jsからdate-fnsへの移行を計画して。
> 以下の観点でマイルストーンを設定して:
> - 影響範囲の小さいユーティリティ関数から開始
> - 共通コンポーネントの移行
> - ページコンポーネントの移行
> - 最終的なmoment.js依存の削除
> 各マイルストーンでマージ可能な状態を維持すること。

並行稼働期間の設計

大規模移行では、新旧が並行して動作する期間を設けることで、リスクを軽減できます。

1
2
> 移行期間中、momentとdate-fnsの両方が使える状態にして。
> 統一のアダプター層を作成し、段階的にdate-fnsに切り替えられるようにして。

アダプター層の例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// dateAdapter.ts - 移行期間中の抽象化層
import { format as dateFnsFormat, addDays as dateFnsAddDays } from 'date-fns';
import moment from 'moment';

// 設定で切り替え可能
const USE_DATE_FNS = process.env.USE_DATE_FNS === 'true';

export function formatDate(date: Date, formatString: string): string {
  if (USE_DATE_FNS) {
    // date-fnsのフォーマット文字列に変換
    const dateFnsFormat = convertFormatString(formatString);
    return dateFnsFormat(date, dateFnsFormat);
  }
  return moment(date).format(formatString);
}

export function addDays(date: Date, days: number): Date {
  if (USE_DATE_FNS) {
    return dateFnsAddDays(date, days);
  }
  return moment(date).add(days, 'days').toDate();
}

// 移行完了後、このファイルを削除してdate-fnsを直接使用

CLAUDE.mdでリファクタリング方針を共有

チーム開発では、リファクタリングの方針をCLAUDE.mdに記述しておくことで、一貫性のある改善が可能になります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
## リファクタリング方針

### 移行中の技術

以下の技術移行を進行中。新規コードは移行先の技術を使用すること。

| 移行元 | 移行先 | 状況 |
|--------|--------|------|
| moment.js | date-fns | 移行中(70%完了)|
| class components | functional components | 移行中(90%完了)|
| callbacks | async/await | 完了 |

### 採用するデザインパターン

- **サービス層**: Strategy Pattern(支払い、通知など)
- **リポジトリ層**: Repository Pattern + Unit of Work
- **DI**: コンストラクタインジェクション

### 禁止事項

- any型の新規追加(既存はリファクタリング時に修正)
- var宣言(const/letを使用)
- コールバック地獄(async/awaitを使用)
- 新規クラスコンポーネントの作成

まとめ

Claude Codeを活用したリファクタリングのポイントを整理します。

  • 非推奨APIの検出と移行: プロジェクト全体をスキャンして非推奨APIを特定し、段階的に移行する
  • 最新仕様への更新: ES2024などの新機能を活用して、コードの可読性と保守性を向上させる
  • デザインパターンの適用: if-elseの連鎖やハードコードされた依存関係を、適切なパターンで置き換える
  • パフォーマンス最適化: N+1クエリ、不要な再レンダリング、非効率なアルゴリズムを特定して改善する
  • テストを維持しながら進める: リファクタリング前にテストを確認・追加し、変更のたびにテストで検証する

Claude Codeのコードベース理解能力を活用することで、単一ファイルにとどまらない、プロジェクト横断的なリファクタリングを安全に実行できます。Plan Modeで影響範囲を確認し、Accept Modeで変更を一つずつ確認しながら、技術的負債を着実に返済していきましょう。

参考リンク