ある朝のことです。私が個人開発で運営しているサイト群のうち、3年ほど手を入れていなかったコードベースを開いて、しばらく画面を見つめたまま動けなくなりました。コールバックが何重にも入れ子になり、型が any で塗り潰され、どのファイルを触れば何が壊れるのか、書いた本人である私自身にももう見通せなくなっていたのです。
大規模リファクタリングの最初のステップは、現在のコードベースの構造を正確に理解することです。Antigravity の Editor View では、プロジェクト全体をコンテキストに含めた状態で AI に分析を依頼できます。
依存関係グラフの生成
Cmd + I(インラインチャット)を開き、以下のようなプロンプトで依存関係を分析します。
Analyze the dependency graph of this project.
Identify circular dependencies, tightly coupled modules,
and suggest the optimal refactoring order based on
dependency depth.
If I rename the UserService class and change its constructor
signature, list every file that would need to be updated,
categorized by risk level (high/medium/low).
// Before: Callback patternfunction fetchUser(id: string, callback: (err: Error | null, user?: User) => void) { db.query(`SELECT * FROM users WHERE id = ?`, [id], (err, rows) => { if (err) return callback(err); callback(null, rows[0] as User); });}// After: Async/Await patternasync function fetchUser(id: string): Promise<User> { const rows = await db.query(`SELECT * FROM users WHERE id = ?`, [id]); return rows[0] as User;}
Editor View で対象ファイル群を選択した状態で、以下のプロンプトを使います。
Convert all callback-based functions in the selected files
to async/await pattern. Preserve error handling semantics,
update all call sites, and add proper TypeScript return types.
Show me the diff before applying.
重要なのは 「Show me the diff before applying」 の一文です。これにより、Antigravity は Diff View で変更内容を提示し、あなたが承認してから適用します。数十ファイルの変更でも、差分を確認してから一括適用できるため安全です。この一文を省くと AI はすぐに書き換えに走るので、私は定型句として必ず添えるようにしています。
// Phase 1: Adapter を作成(旧 API と新 API を橋渡し)class LegacyPaymentAdapter implements PaymentGateway { private legacy: LegacyPaymentService; private modern: ModernPaymentService; private useModern: boolean; constructor(config: { enableModernPayment: boolean }) { this.legacy = new LegacyPaymentService(); this.modern = new ModernPaymentService(); this.useModern = config.enableModernPayment; } async processPayment(amount: number, currency: string): Promise<PaymentResult> { if (this.useModern) { // New path: Modern implementation return this.modern.charge({ amount, currency }); } // Legacy path: Will be removed after migration const result = await this.legacy.oldCharge(amount, currency); return this.normalizeResult(result); } private normalizeResult(legacyResult: LegacyResult): PaymentResult { return { transactionId: legacyResult.tx_id, status: legacyResult.ok ? 'success' : 'failed', timestamp: new Date(legacyResult.ts), }; }}
Editor View のプロンプト例は次の通りです。
Analyze the LegacyPaymentService class and create a
Strangler Fig migration plan:
1. Generate an adapter that wraps the legacy service
2. Define the modern interface based on actual usage patterns
3. Create a feature flag to toggle between legacy and modern
4. List all call sites that need to use the adapter
Generate comprehensive tests for the LegacyPaymentService
before refactoring. Cover: happy path, edge cases,
error scenarios, and boundary conditions.
Use the existing test patterns in this project.
// Before: N+1 query pattern(AI が自動検出)async function getOrdersWithProducts(userId: string) { const orders = await db.query('SELECT * FROM orders WHERE user_id = ?', [userId]); // ⚠️ N+1: Each iteration triggers a separate query for (const order of orders) { order.products = await db.query( 'SELECT * FROM products WHERE order_id = ?', [order.id] ); } return orders;}// After: Batch query pattern(AI が変換)async function getOrdersWithProducts(userId: string) { const orders = await db.query('SELECT * FROM orders WHERE user_id = ?', [userId]); if (orders.length === 0) return []; const orderIds = orders.map(o => o.id); const products = await db.query( `SELECT * FROM products WHERE order_id IN (${orderIds.map(() => '?').join(',')})`, orderIds ); // Group products by order_id const productsByOrder = products.reduce((acc, p) => { (acc[p.order_id] ||= []).push(p); return acc; }, {} as Record<string, Product[]>); return orders.map(order => ({ ...order, products: productsByOrder[order.id] || [], }));}
このような最適化パターンは、以下のプロンプトで検出と修正を一括で行えます。
Scan the repository for N+1 query patterns, unnecessary
re-renders in React components, and synchronous file
operations that could be parallelized. For each finding,
generate the optimized version with before/after comparison.
ふたつ目は、AI が「ついでに」周辺コードまで整えてしまうことです。依頼した範囲を超えて変数名やフォーマットを変えられると、レビューのノイズが一気に増えます。私はプロンプトに「Only modify what is necessary for this change. Do not reformat unrelated code.」と添えることで、変更を最小限に抑えるようにしました。
このスイッチがあるだけで、リファクタリングの心理的な重さがまるで変わります。「失敗しても1行の環境変数で戻せる」と分かっていれば、思い切って前に進めます。Editor View に移行プランを作らせるときも、私は必ず「Include a kill switch and rollback path.」を要件に加えるようにしています。