ソフトウェアの長期保守において、肥大化し複雑化したレガシー関数のリファクタリングは、エンジニアにとって最も神経を使う作業の一つです。深い条件分岐のネスト、コールバック地獄、暗黙の状態変更、そして古い「ゾンビコード」が混在する数百行の関数を手作業で1行ずつ書き直そうとすると、構文エラーの混入やスコープ破壊、予期せぬリグレッション(先祖返り・不具合)を招きやすくなります。
Mutable.ai は、この課題を解決するために Visual Studio Code 拡張機能へ Auto Edit(自動編集) を実装しました。カーソル位置から数行先を予測するだけの単なるコード補完や、ブラウザとエディタの間でコードをコピー&ペーストしなければならない外部チャットとは異なり、Auto Edit はエディタ内で完結する自然言語リファクタリングエンジンです。開発者は対象コードを選択し、日本語や英語などの自然言語で指示を与えるだけで、プロジェクト全体の文脈を考慮した安全かつアトミックなコード改善を実行できます。
1. Auto Edit と従来のコード補完・AI チャットの違い
AI 開発支援ツールの操作形態は、大きく以下の2つに分かれていました。
- インライン補完(ゴーストテキスト): GitHub Copilot に代表される形式で、カーソル位置から直後のコードを推測します。定型コードの生成には有用ですが、関数全体の構造改革や複数箇所にまたがる依存関係の整理には対応できません。
- サイドバー・チャット: チャットパネルで AI にリファクタリング案を尋ねる形式です。しかし、コードをパネルに貼り付け、生成結果をエディタへ手動で移植する必要があり、集中力(フロー状態)が途切れ、細部の不整合も見落としがちになります。
Mutable.ai の Auto Edit は、自然言語による指示と IDE のネイティブ機能を統合しています。
- エディタ内その場での変換(In-Place Transformation): 変更はアクティブなファイルタブ内で直接行われ、インデントや命名規則、ローカルコンテキストを厳密に維持します。
- リポジトリ全体の文脈把握(Codebase Awareness): 単一のファイルだけでなく、プロジェクト構造や依存関係をインデックス化して参照するため、リファクタリング後の関数シグネチャや型定義がプロジェクト全体と整合します。
- ネイティブな差分確認(Visual Diff Inspection): 生成された変更内容は、エディタ内で赤と緑の Diff(差分)表示としてプレビューされ、行単位で安全性を確認してからファイルへ反映できます。
2. VS Code で Auto Edit を呼び出す3つの方法
Mutable.ai 拡張機能では、作業の流れに合わせて以下の3系統のエントリポイントが用意されています。
| 呼び出し方法 | アクセス手順 | 最適なユースケース |
|---|---|---|
| Code Lens | 関数宣言の直上に表示される Auto Edit ラベルをクリック | 単一の関数、メソッド、またはクラスの集中リファクタリング |
| ステータスバー | 画面最下部のステータスバーにある Mutable.ai アイコンをクリック | グローバルコマンドの呼び出しや拡張機能の状態確認 |
| コンテキストメニュー | 選択範囲またはファイルツリーを右クリックし Mutable.ai: Auto Edit を選択 | 複数ブロックの編集やモジュール単位の変更 |
呼び出しを行うと、選択コードの直上にフローティング入力ボックスが表示され、自然言語の指示待ち状態になります。
3. 複雑な関数をリファクタリングする標準ワークフロー
Auto Edit を用いた実践的なリファクタリング手順は以下の5ステップです。
[対象の複雑な関数]
│
▼
[ステップ 1: 範囲選択] ──► [ステップ 2: Auto Edit 起動] ──► [ステップ 3: 明確なプロンプト入力]
│
▼
[ステップ 5: 確定・検証] ◄── [ステップ 4: エディタ内 Diff 確認] ◄── [AI による文脈合成]
ステップ 1: 対象関数のスコープを絞る
リファクタリング対象の関数ブロック全体を選択するか、関数宣言行にカーソルを置きます。明確に対象を囲むことで、隣接する関数やインポート文への予期せぬ改変を防ぎます。
ステップ 2: Code Lens から Auto Edit を起動する
関数ヘッダーの上に表示されている Auto Edit Code Lens をクリックします。選択範囲がコンテキストとして固定され、指示入力バーがアクティブになります。
ステップ 3: 具体的かつ構造的なプロンプトを入力する
「この関数を改善して」のような曖昧な指示は避け、設計意図、構造上の制約、互換性の維持を明確に記述します。
- 「入力バリデーションとレスポンス解析を個別のプライベート関数に抽出し、メイン関数をオーケストレーターとして整理して」
- 「ネストされた if-else 構造をガード節(早期リターン)に置き換えて、ロジックをフラットにして」
- 「Promise のコールバックチェーンを async/await 構文に変換し、型安全な try/catch エラーハンドリングを追加して」
- 「すべての引数と戻り値に厳格な TypeScript の型注釈を追加し、未使用の古い変数とゾンビコードを削除して」
ステップ 4: エディタ内で差分(Diff)を検証する
Mutable.ai が変換コードを生成すると、エディタ内でビジュアル差分が表示されます。
- 赤色ハイライト: 削除されるネストされた不要分岐や過去の遺物コード。
- 緑色ハイライト: 新しく追加されたガード節、抽出された関数、型定義。
呼び出し元の互換性が維持されているか、例外の送出パターンが変わっていないかを厳密に確認します。
ステップ 5: 反映、追加指示、または破棄
- 反映(Accept): Enter キーを押すか
Accept Diffをクリックすると、変更が即座にファイルへ適用されます。 - 追加指示(Iterate): エッジケースに漏れがある場合は、プロンプトバーに追加の指示(例: 「エラーコード ERR_INVALID_PAYLOAD は元の形式のまま維持して」)を入力して再生成させます。
- 破棄(Discard): Esc キーを押すか破棄を選択すれば、ファイルは一切変更されず元の状態に戻ります。
4. 実践例:レガシーな注文処理関数のリファクタリング
以下は、コールバックのネストと暗黙の処理、古いキャンペーンの残骸が混在したレガシーな JavaScript コードの例です。
リファクタリング前(Before)
function processOrder(order, callback) {
if (order) {
if (order.id && order.items && order.items.length > 0) {
var total = 0;
for (var i = 0; i < order.items.length; i++) {
if (order.items[i].price && order.items[i].qty) {
total += order.items[i].price * order.items[i].qty;
} else {
// 2021年の特別キャンペーン用フォールバック(現在は廃止)
if (order.legacyPromo) {
total += 10;
}
}
}
if (order.user && order.user.email) {
database.saveOrder(order.id, total, function(err, res) {
if (err) {
callback(err, null);
} else {
emailService.sendReceipt(order.user.email, res, function(err2) {
if (err2) {
callback(null, { status: "partial_success", res: res });
} else {
callback(null, { status: "success", res: res });
}
});
}
});
} else {
callback(new Error("Missing user email"), null);
}
} else {
callback(new Error("Invalid items payload"), null);
}
} else {
callback(new Error("Empty order"), null);
}
}
Auto Edit への自然言語指示
「processOrder をモダンな TypeScript にリファクタリングしてください。コールバックを async/await に変更し、多重ネストを早期リターンのガード節で平坦化し、合計金額の計算を個別のヘルパー関数に抽出し、legacyPromo の廃止ロジックを削除して、Order と OrderResult のインターフェース型を定義してください。」
リファクタリング後(After)
interface OrderItem {
id: string;
price: number;
qty: number;
}
interface Order {
id: string;
items: OrderItem[];
user: { email: string };
}
interface OrderResult {
status: "success" | "partial_success";
res: unknown;
}
function calculateOrderTotal(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
async function processOrder(order: Order): Promise<OrderResult> {
if (!order) {
throw new Error("Empty order");
}
if (!order.id || !order.items || order.items.length === 0) {
throw new Error("Invalid items payload");
}
if (!order.user?.email) {
throw new Error("Missing user email");
}
const total = calculateOrderTotal(order.items);
const res = await database.saveOrder(order.id, total);
try {
await emailService.sendReceipt(order.user.email, res);
return { status: "success", res };
} catch (emailError) {
return { status: "partial_success", res };
}
}
リファクタリングによる改善点:
- 循環的複雑度の半減: 6段階あったインデントのネストが、平坦なガード節により1段階に縮小。
- ゾンビコードの根絶: 動作保証のない古い
legacyPromo分岐を安全に除去。 - 責務の分離: 金額計算を
calculateOrderTotalに切り出すことで、個別単体テストが容易化。 - コンパイル時の型安全性: TypeScript の型定義により、呼び出し元での入力ミスを防止。
5. ベストプラクティスと注意点
自然言語リファクタリングをチーム開発で成功させるための指針です。
推奨プラクティス
- アトミック(段階的)に進める: 1,000行を超えるモジュール全体を1回のプロンプトで書き直そうとせず、「ガード節への変換」「関数の切り出し」「型注釈の付与」と段階的に適用します。
- 事前にテストを用意する: 既存コードの振る舞いを保護するため、単体テストが存在することを確認した上で作業を行い、Diff 適用直後にテストを実行します。
- プロジェクトの既存パターンを指定する: 指示文の中で「既存のユーティリティモジュールに合わせて」「プロジェクトのエラーハンドリング規約に従って」と文脈を制約します。
避けるべきアンチパターン
- Diff の無検証承認: 生成結果を精査せずに確定することは厳禁です。特に例外のスロー条件や変数のスコープを注視してください。
- 抽象的すぎる指示: 「高速化して」「綺麗にして」といった指示は、意図しないアルゴリズムの置換や可読性の低下を招きます。
- シグネチャの無断変更: 同期関数から非同期関数(Promise)への変更は、呼び出し元すべてに
awaitの追加を要求します。外部インターフェースの維持が必要な場合はプロンプトで明示してください。
6. プロダクトの現状と現代の開発ツール生態系
Mutable.ai は 2021 年に Omar Shams と Maxwell Siegelman によって設立され(Y Combinator W22)、コードベース自動ドキュメント生成(Auto Wiki)やリポジトリ認識型コーディング支援の先駆者として注目を集めました。
2024年12月、Mutable.ai は Alphabet(Google)によって買収されました。この買収に伴い、単独製品としての Mutable.ai サービスおよび VS Code マーケットプレイス上の拡張機能は役目を終え、Google の社内技術基盤へと統合・移行されました。現在スタンドアロン版の新規導入は行われていませんが、Mutable.ai が切り拓いた「エディタ内でのその場自然言語指示」「コードベース全体の文脈把握」「ネイティブな差分確認」という設計思想は、現代の Cursor(Composer)、GitHub Copilot Edits、Continue.dev、Aider などに広く受け継がれ、業界の標準的なリファクタリング手法として定着しています。