はじめに
長年運用されたシステムにおいて、switch文や巨大なif-elseブロックは「技術的負債」の象徴と言えます。機能追加のたびに分岐が増え、可読性は低下し、変更の影響範囲が見えなくなることで、いわゆるスパゲッティコードの温床となります。
オブジェクト指向設計において、この課題に対する解決策は「条件分岐をポリモーフィズム(多態性)へ置き換える」ことです。しかし、既存の巨大なロジックを安全に解体し、インターフェースやクラス群に再構築する作業は、極めて高いリスクと工数を伴います。多くの開発現場で「正解はわかっているが、手が付けられない」状態が続いているのはそのためです。
本稿では、AIエディタ「Cursor」の機能を最大限に活用し、このリファクタリングプロセスを「技術仕様(Protocol)」として定義します。単にAIに「いい感じに直して」と頼むのではなく、入力(現状コード)、処理(構造化されたプロンプト)、出力(疎結合な設計)を厳密に制御することで、エンジニアが設計意図を保ったまま、実装作業をAIに委譲する実践的な方法論です。
これは単なるリファクタリングの自動化ではなく、「AI駆動による設計パターンの適用プロトコル」です。AIを効果的な手段として活用し、ROI(投資対効果)を最大化しながら堅牢なコードベースを取り戻すためのアプローチを解説します。
1. 実装プロトコル概要
1.1 目的とスコープ
本プロトコルは、制御フローの複雑性が限界に達したコードブロック(サイクロマティック複雑度が高いメソッド等)を対象とし、「条件記述のポリモーフィズムによる置換(Replace Conditional with Polymorphism)」パターンを適用することを目的とします。
対象スコープ:
- 3つ以上の分岐を持つ
switch文またはif-else if連鎖 - 分岐ごとに異なるが、概念的に同一の操作を行っているロジック
- 将来的に分岐(タイプコード)の追加が見込まれる箇所
除外スコープ:
- 単純な値の変換(Map等で解決すべきもの)
- 外部システム依存が極端に強く、モック化が困難なレガシーコード(これらは別途「接合部(Seams)」の作成が必要です)
1.2 適用条件
本プロトコルを実行するにあたり、以下の前提条件を満たすことを推奨します。
- Cursor Pro版の利用: 高度な推論能力を持つ
Claudeの最新モデル(Sonnet系列推奨)およびComposer機能の使用を前提とします。- ※旧バージョンのClaudeの最新モデル等はAPI提供が終了している場合があるため、必ず最新のモデル設定を確認してください。
- Gitによるバージョン管理: 各フェーズごとにコミット可能な状態であること。
- 基本的なテストカバレッジ: リファクタリング対象のロジックに対して、少なくとも正常系のテストが通っていること(テストがない場合は、後述する検証フェーズでAIに生成させます)。
1.3 使用するCursor機能定義
本仕様書では、以下の機能を使い分けます。
- Cmd+K (Inline Edit): 単一ファイル内の局所的なコード生成・修正。
- Composer (Cmd+I): 複数ファイルにまたがるクラス生成、インターフェース抽出、ファイル分割。Agent Mode(自律的なタスク実行)が利用可能な場合は、より広範囲な修正を委任できます。
- @Symbols: リファクタリング対象のコンテキストを正確にAIへ伝達するための参照機能。
2. 事前準備:コンテキスト定義仕様
AIによるリファクタリングの品質は、入力される「コンテキスト(文脈)」の精度に比例します。ここでは、Cursorに読み込ませるべき情報を論理的に定義します。
2.1 @Symbols による参照範囲の指定
リファクタリング対象のコードだけでなく、そのコードが依存している型定義や、使用されている箇所(呼び出し元)をコンテキストに含める必要があります。
操作手順:
CursorのチャットまたはComposerを開き、以下のシンボルを@で指定してコンテキストに追加してください。
- Target: リファクタリング対象のメソッドまたはクラス(例:
@OrderProcessor.ts) - Types: 関連するデータ型、Enum定義(例:
@OrderType.ts,@UserContext.ts) - Caller: 対象メソッドを呼び出している主要な箇所(例:
@CheckoutService.ts)
2.2 依存関係の特定プロンプト
目視では見落としがちな「隠れた依存関係」をAIに特定させます。これにより、ロジックを移動した際に変数が未定義になるミスを体系的に防ぎます。
入力プロンプト仕様 (Chat):
# Role
Senior Architect
# Task
Analyze the dependencies in the selected code block (@Target).
# Requirements
以下の項目をリストアップしてください:
1. ローカル変数への依存(引数として渡す必要があるもの)
2. インスタンス変数(this.xxx)への依存
3. 外部モジュール/ライブラリへの依存
4. 副作用(DB書き込み、ログ出力、状態変更)の有無
# Output Format
Markdown list
この分析結果は、後のフェーズでインターフェースのメソッドシグネチャ(引数定義)を決定する際の重要な根拠となります。
2.3 テストコードの有無の確認
既存テストが存在しない場合、リファクタリング前の「振る舞い」を固定するために、AIにテストを生成させます。
入力プロンプト仕様 (Cmd+K):
このメソッドの現在の振る舞いを保証する単体テストを作成してください。
全ての分岐(case)を網羅し、境界値テストも含めること。
テストフレームワークは [Jest/JUnit/RSpec] を使用。
3. Phase 1: 抽象化レイヤーの生成仕様
ここから実装に入ります。まずは、既存の条件分岐ロジックを抽象化するための「インターフェース」または「抽象クラス」を定義します。
3.1 共通インターフェースの抽出プロンプト
既存のコードから「何を行っているか(What)」を抽出し、「どのように行うか(How)」を隠蔽するための定義です。
実行環境: Composer (Cmd+I)
入力プロンプト仕様:
# Context
@Target の [methodName] メソッド内の switch 文をポリモーフィズムを用いてリファクタリングします。
# Instruction
1. 分岐内のロジックに共通する振る舞いを定義する Interface を作成してください。
2. Interface名は [IName] (例: IOrderStrategy) とします。
3. メソッドシグネチャは、先ほどの依存関係分析に基づき、必要なコンテキストを引数として受け取る形にしてください。
# Constraints
- Liskovの置換原則(LSP)を遵守すること。
- メソッド名は具体的かつ直感的なものにすること(execute ではなく calculateDiscount など)。
- ファイルは新規作成すること。
3.2 メソッドシグネチャの定義
AIが生成したインターフェースを確認し、メソッドの引数が適切かレビューします。特に、ステートレスな設計にするために、必要なデータはすべて引数で渡す形式になっているかを確認してください。
チェックポイント:
contextオブジェクトのような巨大なオブジェクトを渡していないか?(必要なデータのみを渡す「Interface Segregation」の観点)- 戻り値の型は明確か?
3.3 命名規則の制御
AIは一般的な名前(Processor, Handlerなど)を付けがちです。ドメイン駆動設計(DDD)の観点から、業務の意味を表す名前になるよう指示を追加します。
修正指示例:
インターフェース名は技術的な役割ではなく、ドメインの意図を表す名前に変更してください。
Bad: ISwitchHandler
Good: IPricingPolicy
4. Phase 2: 具象クラスへのロジック移譲仕様
定義したインターフェースに基づき、条件分岐(case文)の中身を個別のクラスへ移動させます。
4.1 条件ごとのクラス分割指示
Composerを使用して、複数のクラスファイルを一括生成させます。これにより、手作業でのコピー&ペーストによるミスを排除し、効率的なプロジェクト進行を実現します。
実行環境: Composer (Cmd+I)
入力プロンプト仕様:
# Task
Create concrete classes implementing [InterfaceName] for each case in the switch statement of @Target.
# Requirements
1. 各 case 文のロジックを対応するクラスのメソッドに移動してください。
2. クラス名は [Condition] + [StrategyName] としてください(例: PremiumPricingPolicy)。
3. 元のコードにある `break` や `return` の意図を汲み取り、適切に実装すること。
4. 共通処理がある場合は、別途抽象クラスを作成して継承させるか、ユーティリティへ切り出す提案をしてください。
# Output
各クラスを個別のファイルとして作成。
4.2 ロジックの移動と適応
単純な移動だけでなく、AIにコードの最適化を行わせます。元のコードでネストが深かったり、不要な変数が存在した場合は、このタイミングでクリーンアップさせます。
コンテキスト変数の注入:
元のメソッド内で参照されていたローカル変数は、具象クラス内では参照できません。Phase 1で定義したメソッド引数、またはコンストラクタ経由でデータを受け取るように修正されているか、厳密にチェックします。
実践のTips:
AIは時折、プライベートメソッドへの依存を見落とすことがあります。移動先のクラスから元のクラスのプライベートメソッドを呼び出そうとしていないか確認し、必要であればそのメソッドも移動するか、publicにする(あるいはHelperクラスへ移動する)判断をしてください。
5. Phase 3: ファクトリパターンの実装仕様
条件分岐を完全に消去するわけにはいきません。「どのクラスを使うか」を決定するロジックはどこかに残る必要があります。これをFactoryクラス(生成の責務)へ集約します。
5.1 分岐ロジックのファクトリへの集約
元のswitch文を、適切なStrategyクラスをインスタンス化して返すだけのシンプルなFactoryメソッドへ変換します。
実行環境: Composer (Cmd+I) または Cmd+K
入力プロンプト仕様:
# Task
Create a Factory class to instantiate [InterfaceName].
# Requirements
1. クラス名: [Name]Factory
2. メソッド: `create(type: OrderType): [InterfaceName]`
3. 元の switch 文の分岐条件を使用して、適切な具象クラスのインスタンスを返却すること。
4. 未知のタイプが渡された場合は、例外を投げるか、デフォルトの戦略(NullObjectパターンなど)を返すこと。
5.2 クライアントコードの修正
最後に、元の巨大なメソッド(@Target)を修正し、Factoryを使って処理を委譲するように書き換えます。
入力プロンプト仕様 (Cmd+K):
Refactor the original method to use the [Name]Factory.
1. Factoryを使って戦略クラスを取得。
2. 取得した戦略クラスのメソッドを実行。
3. 古い switch 文とロジックを削除。
期待されるコードの変化(例):
Before:
function calculatePrice(order) {
let price = 0;
switch (order.type) {
case 'REGULAR':
// 50行のロジック
break;
case 'PREMIUM':
// 60行のロジック
break;
// ...
}
return price;
}
After:
function calculatePrice(order) {
const pricingPolicy = PricingPolicyFactory.create(order.type);
return pricingPolicy.calculate(order);
}
5.3 DIコンテナとの連携(オプション)
Spring BootやNestJSなどのフレームワークを使用している場合、Factoryを手動で作るのではなく、DIコンテナの機能(Map注入など)を使って解決できる場合があります。この場合、プロンプトに「DIコンテナの機能を使って解決する提案をして」と含めると、よりモダンな実装が提示されます。
6. 検証と安全性保証
リファクタリングは「振る舞いを変えない」ことが大原則です。AIが生成したコードが元のロジックと完全に等価であることを検証します。
6.1 自動生成テストによる振る舞いの検証
事前準備で作成したテストコードを実行します。もしテストが失敗した場合、それはリファクタリングによるバグ(回帰)です。
デバッグプロンプト仕様:
# Issue
The test [TestName] failed after refactoring.
# Task
Compare the logic in the original switch case (provided below) with the new Concrete Class implementation.
Identify the discrepancy and fix the Concrete Class.
[Original Logic Snippet]
6.2 エッジケースの挙動確認
特に注意すべきはdefault節の扱いです。元のコードが何もせずにスルーしていたのか、エラーを投げていたのか、デフォルト値を設定していたのか。ポリモーフィズム化によって「例外」の挙動が変わっていないかを確認します。
6.3 コンパイルエラーおよび実行時エラーのチェックリスト
Cursorのターミナル連携機能を使い、プロジェクト全体のビルドとテストを一括実行して健全性を確認します。
- 型整合性: インターフェースの型定義と実装が一致しているか。
- インポート漏れ: ファイル分割によりimport文が不足していないか。
- 循環参照: Factoryと具象クラス、元のクラス間で循環参照が発生していないか。
まとめ
本稿で定義したプロトコルに従えば、Cursor AIは単なるコーディングアシスタントではなく、「設計リファクタリングの実行エンジン」として機能します。
- コンテキスト定義: 依存関係を明示し、AIに視野を与える。
- 抽象化: 共通インターフェースを定義し、契約を結ぶ。
- 具象化: ロジックを分割し、責務を単一にする。
- 集約: Factoryパターンで生成責任を分離する。
- 検証: テスト駆動で安全性を担保する。
このプロセスは、一度習得すれば他の設計パターン(Template Method, Chain of Responsibilityなど)の適用にも応用可能です。スパゲッティコードとの戦いは、根性論ではなく、正しいプロトコルとツールの活用によって終結させることができます。AIを効果的な手段として活用し、プロジェクトの品質と開発効率の向上に役立ててください。
コメント