はじめに

リファクタリングは、外部から見た振る舞いを変えずにコードの内部構造を改善する作業です。技術的負債の解消、可読性の向上、テスタビリティの改善など、その恩恵は大きいものの、大規模なリファクタリングには相応のリスクと工数が伴います。

OpenAI Codexは、このリファクタリング作業を大幅に効率化できます。タスクの分解と計画立案、コードの一括変更、影響範囲の確認、テストの自動実行まで、人間が最も時間を費やす反復的な作業をCodexに委任することで、開発者は設計判断やレビューといった本質的な作業に集中できます。

本記事では、大規模なリファクタリングを安全かつ効率的にCodexと協働して実行する方法を解説します。

前提条件

本記事の内容を実践するには、以下の準備が必要です。

要件 詳細
ChatGPTプラン Plus、Pro、Business、Edu、Enterpriseのいずれか
GitHub連携 Codexとの接続設定が完了していること
環境設定 対象リポジトリの環境が作成済みであること
テスト環境 自動テストが実行可能な状態であること

基本操作については、シリーズの前回までの記事を参照してください。

リファクタリングにおけるCodexの強み

Codexがリファクタリングで力を発揮する理由は、以下の特性にあります。

flowchart TD
    A[大規模コンテキスト理解] --> B[依存関係の把握]
    B --> C[影響範囲の特定]
    C --> D[一括変更の実行]
    D --> E[テスト実行・検証]
    E --> F{全テスト成功?}
    F -->|No| G[修正の反復]
    G --> D
    F -->|Yes| H[PR作成]

従来のIDEリファクタリング機能と比較した場合、Codexには以下のような優位性があります。

観点 IDEのリファクタリング機能 Codex
対象範囲 単一の変換パターン 複数パターンの組み合わせ
コンテキスト 構文レベル 意味・設計レベル
判断 機械的な変換 文脈に応じた最適化
検証 手動でテスト実行 自動でテスト実行・修正
ドキュメント 変更なし 必要に応じて更新

リファクタリングタスクの分解と計画

段階的なアプローチの重要性

大規模なリファクタリングを一度に実行しようとすると、以下のリスクが生じます。

  • 変更の追跡が困難になる
  • 問題発生時の原因特定が難しい
  • レビューの負担が増大する
  • ロールバックが複雑になる

Codexを活用する場合でも、タスクを適切な粒度に分解することが成功の鍵です。

Codexによる計画立案

まず、Codexに現状分析とリファクタリング計画の立案を依頼します。IDE拡張機能で$planスキルを使用すると、構造化された計画を得られます。

計画立案のプロンプト例

$plan

認証サブシステムをリファクタリングする計画を立ててください。

現在の問題:
- 認証ロジックが複数のサービスに分散している
- トークン検証、セッション管理、権限チェックが密結合
- 循環参照が発生している
- ユニットテストが書きにくい構造

目標:
- 責務の明確な分離(トークンパース/セッション読み込み/権限判定)
- 循環参照の解消
- テスタビリティの向上

制約:
- ユーザーから見た振る舞いは変更しない
- 公開APIの互換性を維持する
- 段階的にマージ可能なマイルストーンに分割する

各マイルストーンで以下を含めてください:
- 移動・変更するファイルの一覧
- 期待される成果物
- ロールバック戦略

計画のレビューと調整

Codexが生成した計画に対して、人間がレビューし、必要に応じて調整を依頼します。

調整の指示例

計画を以下の観点で修正してください:

- マイルストーン2のスコープが大きすぎるため、
  さらに2-aと2-bに分割
- 各マイルストーンに影響を受けるAPIエンドポイントを明記
- CI/CDパイプラインへの影響も考慮に入れる

命名規則の統一

プロジェクト全体での一貫性

命名規則の統一は、コードの可読性と保守性を向上させる基本的なリファクタリングです。Codexは、既存のコードパターンを学習し、プロジェクト全体で一貫した命名を適用できます。

AGENTS.mdでの命名規則定義

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# AGENTS.md

## 命名規則

### 変数・関数
- camelCase を使用(例: `getUserProfile`- 真偽値は is/has/can/should プレフィックス(例: `isActive`- 配列は複数形(例: `users``orderItems`
### クラス・型
- PascalCase を使用(例: `UserService`- インターフェースは I プレフィックスを付けない
- 型エイリアスは説明的な名前(例: `UserId` ではなく `string`
### ファイル
- コンポーネント: PascalCase(例: `UserProfile.tsx`- ユーティリティ: camelCase(例: `formatDate.ts`- 定数: SCREAMING_SNAKE_CASE.ts(例: `API_ENDPOINTS.ts`
### 禁止パターン
- 1文字変数(ループカウンタ以外)
- 略語の多用(例: `usrSvc``userService`- 型を名前に含める(例: `userArray``users`

命名統一タスクの実行

命名規則の統一を依頼する際は、範囲と優先順位を明確にします。

プロンプト例

以下の命名規則違反を修正してください。

対象ディレクトリ: src/services/

修正対象:
1. 略語を使った変数名を正式名称に変更
   - `usr` → `user`
   - `msg` → `message`
   - `svc` → `service`

2. 真偽値変数にis/has/canプレフィックスを追加
   - `active` → `isActive`
   - `permission` → `hasPermission`

3. 配列変数を複数形に統一
   - `userList` → `users`
   - `itemArray` → `items`

変更後:
- 全テストが通ることを確認
- 変更したファイル一覧を報告
- 影響を受ける呼び出し元も同時に修正

命名変更の影響範囲確認

Codexは変更の影響範囲を自動的に追跡し、関連するすべてのファイルを更新します。

[変更開始] src/services/UserService.ts
[分析] getUserData → getUserProfile への変更
[影響範囲] 以下のファイルで参照を発見:
  - src/controllers/UserController.ts (3箇所)
  - src/routes/api/users.ts (1箇所)
  - src/tests/services/UserService.test.ts (5箇所)
[更新] 全ての参照を更新中...
[テスト実行] npm run test -- --grep "UserService"
[結果] 12/12 テスト成功

設計パターンの適用

パターン適用の考え方

設計パターンの適用は、単なるコードの書き換え以上の判断を要します。Codexに依頼する際は、「なぜそのパターンを適用するのか」という目的を明確に伝えることが重要です。

Strategyパターンへのリファクタリング

条件分岐が複雑化したコードに対して、Strategyパターンを適用する例を見てみましょう。

変更前のコード(条件分岐の肥大化)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class PaymentProcessor {
  processPayment(method: string, amount: number): PaymentResult {
    if (method === 'credit_card') {
      // クレジットカード処理(50行)
      // ...
    } else if (method === 'bank_transfer') {
      // 銀行振込処理(40行)
      // ...
    } else if (method === 'convenience_store') {
      // コンビニ払い処理(60行)
      // ...
    } else if (method === 'carrier_billing') {
      // キャリア決済処理(45行)
      // ...
    }
    // 新しい決済方法を追加するたびに分岐が増える
  }
}

Codexへのプロンプト

PaymentProcessorクラスをStrategyパターンでリファクタリングしてください。

目的:
- 新しい決済方法の追加を容易にする
- 各決済方法のロジックを独立してテスト可能にする
- 開放閉鎖原則(OCP)に従った設計にする

要件:
1. PaymentStrategyインターフェースを作成
2. 各決済方法を個別のStrategyクラスに分離
3. PaymentProcessorはStrategyを受け取って処理を委譲
4. 既存のテストが通ることを確認
5. 各Strategyクラスのユニットテストを追加

ファイル構成:
- src/payments/strategies/PaymentStrategy.ts(インターフェース)
- src/payments/strategies/CreditCardStrategy.ts
- src/payments/strategies/BankTransferStrategy.ts
- src/payments/strategies/ConvenienceStoreStrategy.ts
- src/payments/strategies/CarrierBillingStrategy.ts
- src/payments/PaymentProcessor.ts(リファクタリング後)

変更後のコード(Strategyパターン適用)

 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
// PaymentStrategy.ts
export interface PaymentStrategy {
  processPayment(amount: number): PaymentResult
  getSupportedMethod(): string
}

// CreditCardStrategy.ts
export class CreditCardStrategy implements PaymentStrategy {
  getSupportedMethod(): string {
    return 'credit_card'
  }

  processPayment(amount: number): PaymentResult {
    // クレジットカード固有の処理
  }
}

// PaymentProcessor.ts
export class PaymentProcessor {
  private strategies: Map<string, PaymentStrategy>

  constructor(strategies: PaymentStrategy[]) {
    this.strategies = new Map(
      strategies.map(s => [s.getSupportedMethod(), s])
    )
  }

  processPayment(method: string, amount: number): PaymentResult {
    const strategy = this.strategies.get(method)
    if (!strategy) {
      throw new UnsupportedPaymentMethodError(method)
    }
    return strategy.processPayment(amount)
  }
}

複数パターンの組み合わせ

実際のリファクタリングでは、複数のパターンを組み合わせることもあります。

プロンプト例(Factory + Strategyの組み合わせ)

決済システムに以下のパターンを組み合わせて適用してください:

1. Factory Method パターン
   - PaymentStrategyFactoryを作成
   - 決済方法の種類に応じたStrategy生成を担当

2. Dependency Injection
   - PaymentProcessorはコンストラクタでStrategyを受け取る
   - テスト時にモック注入を可能にする

3. Null Object パターン
   - 不明な決済方法にはNullPaymentStrategyを返す
   - 例外の代わりにエラーレスポンスを返す

制約:
- 既存のAPIシグネチャは維持
- 後方互換性を保つため、旧メソッドはdeprecatedとして残す

レガシーコードのモダナイズ

モダナイズの戦略

レガシーコードのモダナイズは、一度に完了させるのではなく、段階的に進めることが重要です。Codexを活用したモダナイズの典型的なアプローチを紹介します。

flowchart TD
    A[現状分析] --> B[テストカバレッジ確保]
    B --> C[段階的な移行]
    C --> D[互換性レイヤー作成]
    D --> E[新実装への切り替え]
    E --> F[旧コード削除]
    
    subgraph "各段階でCodexを活用"
        B --> B1[既存コードのテスト生成]
        C --> C1[新実装の作成]
        D --> D1[アダプター作成]
    end

コールバックからPromise/async-awaitへの移行

Node.jsの古いコードベースでよく見られる、コールバック形式からPromise形式への移行例です。

変更前のコード(コールバック形式)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function getUserOrders(userId, callback) {
  db.query('SELECT * FROM users WHERE id = ?', [userId], (err, user) => {
    if (err) return callback(err);
    if (!user) return callback(new Error('User not found'));
    
    db.query('SELECT * FROM orders WHERE user_id = ?', [userId], (err, orders) => {
      if (err) return callback(err);
      
      const results = orders.map(order => {
        return new Promise((resolve, reject) => {
          db.query('SELECT * FROM order_items WHERE order_id = ?', 
            [order.id], (err, items) => {
              if (err) reject(err);
              else resolve({ ...order, items });
            });
        });
      });
      
      Promise.all(results)
        .then(ordersWithItems => callback(null, { user, orders: ordersWithItems }))
        .catch(err => callback(err));
    });
  });
}

Codexへのプロンプト

以下のレガシーコードをモダンなasync/await形式に移行してください。

対象: src/services/legacy/

移行要件:
1. コールバック形式 → async/await形式
2. Promise.allの活用による並列処理の最適化
3. try/catchによるエラーハンドリング
4. TypeScript型定義の追加

互換性要件:
- 既存のコールバック形式APIは非推奨として残す
- 新APIは同名に「Async」サフィックスを付けない
  (旧APIに「Legacy」サフィックスを付ける)
- 移行期間中は両方のAPIが動作すること

テスト要件:
- 既存テストが引き続き通ること
- 新APIのテストを追加
- エラーケースのテストを網羅

変更後のコード(async/await形式)

 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
interface OrderWithItems extends Order {
  items: OrderItem[]
}

interface UserOrdersResult {
  user: User
  orders: OrderWithItems[]
}

async function getUserOrders(userId: string): Promise<UserOrdersResult> {
  const user = await db.queryOne<User>(
    'SELECT * FROM users WHERE id = ?',
    [userId]
  )
  
  if (!user) {
    throw new UserNotFoundError(userId)
  }
  
  const orders = await db.query<Order>(
    'SELECT * FROM orders WHERE user_id = ?',
    [userId]
  )
  
  const ordersWithItems = await Promise.all(
    orders.map(async (order) => {
      const items = await db.query<OrderItem>(
        'SELECT * FROM order_items WHERE order_id = ?',
        [order.id]
      )
      return { ...order, items }
    })
  )
  
  return { user, orders: ordersWithItems }
}

// 後方互換性のためのレガシーAPI(非推奨)
/** @deprecated getUserOrders()を使用してください */
function getUserOrdersLegacy(
  userId: string,
  callback: (err: Error | null, result?: UserOrdersResult) => void
): void {
  getUserOrders(userId)
    .then(result => callback(null, result))
    .catch(err => callback(err))
}

CommonJSからES Modulesへの移行

プロンプト例

プロジェクト全体をCommonJSからES Modules形式に移行してください。

対象: src/ 以下全体

変更内容:
1. require/module.exports → import/export
2. package.jsonに"type": "module"を追加
3. 相対インポートに.js拡張子を追加
4. __dirnameやrequire.resolveの代替実装

注意事項:
- 動的インポートが必要な箇所は適切にimport()を使用
- jest.config.jsなど、ESM対応していない設定ファイルは.cjsに変更
- node_modulesの依存関係との互換性を確認

段階的な移行:
1. まずsrc/utils/から開始
2. 次にsrc/services/
3. 最後にsrc/index.tsとエントリーポイント

影響範囲の確認とテスト実行

依存関係の可視化

リファクタリング前に、変更対象の依存関係を把握することが重要です。Codexに依存関係の分析を依頼できます。

プロンプト例

src/services/AuthService.tsをリファクタリングする前に、
依存関係を分析してください。

報告内容:
1. AuthServiceが依存しているモジュール(import先)
2. AuthServiceに依存しているモジュール(import元)
3. 循環参照の有無
4. 影響を受けるテストファイル
5. 公開APIとして利用されているメソッド

可能であれば、依存関係をMermaidダイアグラムで図示してください。

Codexの分析結果例

graph TD
    subgraph "AuthServiceの依存先"
        AS[AuthService]
        AS --> TR[TokenRepository]
        AS --> UR[UserRepository]
        AS --> HC[HashingConfig]
        AS --> JW[JWTUtils]
    end
    
    subgraph "AuthServiceの依存元"
        UC[UserController] --> AS
        MC[MiddlewareChain] --> AS
        AC[AdminController] --> AS
        TS[TestSetup] --> AS
    end

テスト戦略

Codexはリファクタリング後に自動でテストを実行しますが、テスト戦略を明示することで、より確実な検証が可能です。

AGENTS.mdでのテスト設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# AGENTS.md

## リファクタリング時のテスト手順

リファクタリングタスクでは、以下の順序でテストを実行してください。

### 1. 変更前の状態確認
~~~bash
git status
npm run test -- --coverage

2. 変更対象に関連するテストの特定

変更するファイルに対応するテストファイルを特定し、 まず関連テストのみを実行:

1
npm run test -- --testPathPattern="<対象ディレクトリ>"

3. 変更後の検証

静的解析

1
2
npm run lint
npm run type-check

ユニットテスト

1
npm run test

統合テスト(該当する場合)

1
npm run test:integration

4. カバレッジ確認

リファクタリング後にカバレッジが低下していないことを確認:

1
npm run test -- --coverage

カバレッジが低下した場合は、追加テストを作成してから PRを作成してください。


### 段階的なテスト実行

大規模なリファクタリングでは、変更のたびにすべてのテストを実行すると時間がかかります。Codexに段階的なテスト実行を指示できます。

**プロンプト例**

リファクタリングを以下の段階で進め、各段階でテストを実行してください。

段階1: インターフェース定義の追加

  • 新規ファイルの追加のみ
  • 既存コードへの影響なし
  • 型チェックのみ実行

段階2: 既存クラスへのインターフェース実装

  • 各クラスにimplementsを追加
  • 影響を受けるファイルのみテスト

段階3: 依存性注入への切り替え

  • コンストラクタの変更
  • 関連するすべてのテストを実行

段階4: 旧コードの削除

  • 全テストスイートを実行
  • カバレッジレポートを生成

各段階完了時に、テスト結果と次の段階への進行可否を報告してください。


## クラウド環境での大規模リファクタリング

### ローカルからクラウドへの委任

IDE拡張機能で計画を立てた後、実際の実装作業をクラウド環境に委任することで、ローカル環境を開放したまま長時間のタスクを実行できます。

**ワークフロー**

1. IDEで現在の作業をコミットまたはスタッシュ
2. 計画をCodexと策定
3. クラウドアイコンをクリックしてクラウド環境を選択
4. 各マイルストーンの実装を委任

**プロンプト例(クラウド委任)**

先ほど策定した計画のマイルストーン1を実装してください。

マイルストーン1の内容:

  • TokenParserクラスの抽出
  • 関連テストの移動と更新
  • 影響を受けるインポートの修正

完了条件:

  • 全テストが通ること
  • 型エラーがないこと
  • lintエラーがないこと

完了後、変更内容のサマリーを報告してください。


### 並列タスクの活用

独立したモジュールのリファクタリングは、複数のクラウドタスクとして並列実行できます。

~~~mermaid
flowchart LR
    A[計画策定] --> B[タスク分割]
    B --> C1[タスク1: ユーザーモジュール]
    B --> C2[タスク2: 注文モジュール]
    B --> C3[タスク3: 決済モジュール]
    C1 --> D[統合テスト]
    C2 --> D
    C3 --> D
    D --> E[PR作成]

並列タスク作成の例

以下の3つのモジュールを並列でリファクタリングしてください。
各タスクは独立して実行可能です。

タスク1: src/modules/user/
- 命名規則の統一
- 型定義の追加
- テストカバレッジ80%以上

タスク2: src/modules/order/
- コールバックをasync/awaitに変換
- エラーハンドリングの統一
- テストカバレッジ80%以上

タスク3: src/modules/payment/
- Strategyパターンの適用
- インターフェースの抽出
- テストカバレッジ80%以上

全タスク完了後、統合テストを実行して
相互の影響がないことを確認してください。

リファクタリングのベストプラクティス

効果的なリファクタリングのために

Codexを活用したリファクタリングを成功させるためのベストプラクティスをまとめます。

カテゴリ ベストプラクティス
計画 大きなタスクは必ず分割し、各マイルストーンを明確に定義する
コミュニケーション 目的と制約を明確に伝え、曖昧な指示を避ける
テスト リファクタリング前にテストカバレッジを確保する
レビュー Codexの変更は必ず人間がレビューしてからマージする
ドキュメント 設計変更の理由をPRの説明やコメントに記録する

アンチパターン

避けるべきアプローチも把握しておきましょう。

  • 一度に広範囲を変更しようとする
  • テストなしでリファクタリングを進める
  • 「とりあえず動くようにして」という曖昧な指示
  • 変更理由を記録せずにマージする
  • 複数の関心事を1つのPRに詰め込む

継続的な改善

リファクタリングは一度で完了するものではありません。Codexを活用して、継続的にコード品質を改善する習慣を構築しましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# AGENTS.md(継続的改善セクション)

## 定期的なリファクタリングタスク

以下のタスクを定期的に実行してコード品質を維持してください:

### 週次
- 未使用のインポート・変数の削除
- 型定義の厳格化(any型の削減)

### 月次
- 複雑度の高い関数の分割(循環的複雑度10以上)
- 重複コードの抽出と共通化

### 四半期
- 依存関係の整理と循環参照の解消
- 設計パターンの見直しと適用

まとめ

本記事では、OpenAI Codexを活用した大規模リファクタリングの実践方法を解説しました。

主要なポイントを振り返ると以下のとおりです。

  • タスクの分解と計画立案をCodexに依頼し、人間がレビュー・調整する
  • 命名規則の統一はAGENTS.mdで定義し、プロジェクト全体に一貫して適用する
  • 設計パターンの適用は目的を明確にして依頼し、段階的に進める
  • レガシーコードのモダナイズは互換性レイヤーを設けて段階的に移行する
  • 影響範囲の確認とテスト実行を自動化し、安全なリファクタリングを実現する

リファクタリングは、開発者の判断力とCodexの実行力を組み合わせることで、より安全かつ効率的に進められます。次回の記事では、テストカバレッジの向上をテーマに、Codexを活用した自動テスト生成の実践を解説します。

参考リンク