画面の向こうで、あなたが今見つめているそのコード。数年前に退職した誰かが書いた、継ぎ接ぎだらけの巨大な関数。変数名は data や temp ばかりで、コメントは実装と矛盾しているかもしれない。そして何より恐ろしいのは、「なぜか動いている」という事実ではないでしょうか。
「動いているものには触るな」
この業界の鉄則とも言える言葉ですが、同時に私たちの首を真綿で締める呪いでもあります。機能追加のたびにバグのリスクに怯え、調査に膨大な時間を費やす。経営者視点で見れば、こうした「技術的負債」の利払いに貴重なエンジニアリングリソースを浪費することは、ビジネスのスピードを著しく削ぐ致命的な問題です。
国内外の多くのスタートアップや大規模な開発現場でも、この悩みは共通しています。しかし、現代の開発現場には強力な味方がいます。そう、AIエージェントやGitHub Copilotなどの生成AIです。
ただ、誤解しないでください。「AIにコードを投げれば、魔法のようにきれいになる」なんて甘い話をするつもりはありません。AIは優秀ですが、指示の出し方を間違えれば、動かないコードを平気で吐き出す「危なっかしい新人」にもなり得ます。確率論的に次の単語を予測するLLM(大規模言語モデル)の性質上、論理的な整合性よりも「それっぽさ」を優先してしまうことがあるからです。
今回は、長年の開発現場で培った知見をベースに、最新技術の可能性と実用性をバランスよく取り入れ、「AIを安全に使って、複雑なコードを少しずつほぐしていくための技術」を解説します。大規模なリアーキテクチャではなく、今日から手元のファイルで始められる、地に足のついたアプローチです。まずは動くものを作り、仮説を即座に形にして検証するプロトタイプ思考で、コードへの恐怖心を自信に変えていきましょう。
なぜ「AI×リファクタリング」がエンジニアの救世主になるのか
多くのエンジニアがリファクタリングを躊躇する最大の理由は「恐怖」です。「自分が修正したせいで、システムが停止したらどうしよう」というプレッシャーは、創造性を奪います。ここでAIが果たす役割は、単なる自動化ツール以上のものです。
「壊すかもしれない」という恐怖心の緩和
リファクタリングは、外科手術に似ています。複雑に絡み合った血管(依存関係)を傷つけずに、患部(悪いコード)を取り除かなければなりません。これを一人で行うのは孤独で恐ろしい作業です。
AIを活用することは、この手術室に「冷静沈着な熟練助手」を招き入れるようなものです。AIは感情を持ちません。「こんな汚いコードを書いたのは誰だ」と怒ることもなければ、「もう疲れたから適当に直そう」と妥協することもありません。提示されたコードの論理構造だけを冷徹に分析し、パターンに基づいて改善案を出してくれます。
「この変更を加えると、こちらのロジックに影響が出る可能性があります」とAIに指摘されるだけで、見落としていたリスクに気づける。この安心感が、エンジニアの一歩を後押ししてくれるのです。
AIが得意な「パターン認識」と「構造変換」
人間はコードを「物語(文脈)」として読もうとしますが、スパゲッティコードには物語がありません。だから読むのが辛いのです。一方で、LLMはコードを「トークンのパターン」として処理します。
- 命名規則の不統一
- 深いネスト構造(サイクロマティック複雑度が高い状態)
- 重複したロジック(DRY原則違反)
これらはAIにとって、最も検知しやすい「典型的なパターン」です。人間が数時間かけて解読する複雑な条件分岐も、AIなら一瞬で論理式として捉え、等価でシンプルな形に変換(トランスフォーム)できます。この「構造変換能力」こそが、AIリファクタリングの真骨頂です。
自動化ではなく「ペアプログラミング」と捉える重要性
ここで重要なマインドセットがあります。それは、AIに「丸投げ」しないことです。
「このコードをリファクタリングして」とだけ投げるのは、新人に「いい感じにしておいて」と指示するのと同じくらい危険です。AIは文脈(ドメイン知識やビジネスルール)を知りません。
推奨するのは、AIとのペアプログラミングです。
- エンジニア:「この関数、読みづらいから整理したいんだけど、どう思う?」
- AI:「条件分岐が複雑ですね。ガード節を使って早期リターンすれば、ネストを2段階浅くできますよ」
- エンジニア:「なるほど、じゃあその方針でドラフトを書いてみて」
このように対話をしながら進めることで、主導権を人間が持ちつつ、AIの演算能力を借りる。これが最も安全で、かつ学習効果の高いリファクタリング手法です。
さて、AIをパートナーとして迎える心構えができたところで、次は具体的にどう指示を出せば良いのか、その「伝え方」の技術について見ていきましょう。
準備編:AIに意図を伝える「3つの基本プロンプト構造」
AIリファクタリングで失敗する原因の多くは「プロンプト(指示)」の曖昧さにあります。AIに正確な仕事をさせるためには、こちらの意図を明確な「型」に落とし込む必要があります。
実務の現場では、以下の3つの要素を含んだプロンプトを使用することが推奨されます。これをテンプレートとして辞書登録しておくと便利です。
1. 役割定義(Role):君はシニアアーキテクトだ
まず、AIに「誰として振る舞うべきか」を定義します。単に「コードを直して」と言うのと、「熟練のソフトウェアアーキテクトとして、保守性を最優先に修正して」と言うのとでは、出力されるコードの品質に差が出ます。役割を与えることで、AIはそのドメイン(領域)に関連する専門用語やベストプラクティスを優先的に呼び出すようになります。
2. 制約条件(Constraint):振る舞いを変えずに構造だけ変える
リファクタリングの定義は「外部から見た振る舞いを変えずに、内部構造を改善すること」です。しかし、AIは気を利かせて勝手に機能を追加したり、仕様を変えたりすることがあります。
これを「ハルシネーション(幻覚)」と呼びます。AIは事実に基づかない情報をあたかも真実のように生成することがあります。コード生成においては、存在しないライブラリの関数を捏造したり、仕様を勝手に解釈変更したりする現象として現れます。
これを防ぐために、「やってはいけないこと」を強く制約として与えます。
3. 出力形式(Format):Before/Afterと解説をセットにする
修正後のコードだけをポンと渡されても、どこが変わったのか確認するのは大変です。変更点(Diff)と、なぜそう変えたのかという理由をセットで出力させることで、レビューの効率を劇的に高めます。
【コピペで使える】基本プロンプトテンプレート
## Role
あなたは経験豊富なシニアソフトウェアエンジニアです。
可読性が低く、保守が困難なレガシーコードのリファクタリングを行います。
## Constraints
- コードの外部的な振る舞い(ロジックの結果)は絶対に変更しないこと。
- 新たな機能追加は行わないこと。
- 既存のライブラリや依存関係を変更しないこと。
- TypeScriptの型定義は厳密に行うこと(anyは禁止)。
## Input Code
(ここにリファクタリングしたいコードを貼り付ける)
## Output Format
以下の形式で出力してください。
1. リファクタリングのポイント(箇条書きで3点以内)
2. 修正後のコード(コードブロック)
3. 修正箇所の解説(なぜその変更が必要だったか)
この「型」を使うだけで、AIの回答精度は安定します。プロンプトエンジニアリングは難しく考える必要はありません。要は「誰に」「何を」「どうしてほしいか」を明確にするコミュニケーションそのものです。
では、このテンプレートを使って、実際にコードを改善していくステップに入りましょう。まずは最もリスクの低いアプローチからです。
実践ステップ1:リスクゼロから始める「可読性の向上」
いきなりロジックを書き換えるのは怖いですし、リスクも高いです。まずは、コードの挙動には一切影響を与えずに、「人間にとっての読みやすさ」だけを向上させるステップから始めましょう。これは、AIへの信頼を築くための準備運動でもあります。
変数名・関数名の意図明確化
レガシーコードでよく見るのが、x, flag, data といった抽象的すぎる変数名です。文脈を理解していないと適切な名前は付けられませんが、AIはコード全体の流れから変数の役割を推測するのが得意です。
悪いコード例(Python):
def c(d):
t = 0
for i in d:
if i > 100:
t += i * 1.1
return t
これでは何をしているのかさっぱり分かりませんね。ここでAIに以下のプロンプトを投げます。
プロンプト:
「上記のコードのロジックは変えずに、変数名と関数名をその役割を表す適切な英語に変更してください。また、Type Hint(型ヒント)を追加してください。」
AIによる改善例:
def calculate_total_price_with_tax(prices: list[float]) -> float:
total_price = 0.0
for price in prices:
if price > 100:
# 100を超える場合は税率10%を加算(と推測)
total_price += price * 1.1
return total_price
どうでしょう? ロジックは全く同じですが、意味が明確になりました。これなら、もしロジックに間違いがあっても(例えば税率が変わった時など)、すぐに気づくことができます。
マジックナンバーの定数化
上記のコードにある 100 や 1.1 といった数字。これらは「マジックナンバー」と呼ばれ、保守性の敵です。AIにこれらを定数として切り出すよう指示しましょう。
プロンプト:
「コード内に含まれるマジックナンバーを、意味のある定数(大文字)として定義し、置き換えてください。」
AIは THRESHOLD_PRICE = 100 や TAX_RATE = 1.1 といった定数を定義してくれるはずです。もし TAX_RATE という名前が文脈的に正しくなければ(例えば手数料だった場合)、人間がそこだけ修正すれば良いのです。
複雑なロジックへのコメント・ドキュメント生成
直接触りたくない複雑怪奇なロジックがある場合、無理に直そうとせず、まずは「解説コメント」をAIに書かせるのも有効です。
プロンプト:
「この関数は非常に複雑です。行ごとの処理内容を分析し、コードの意図を説明する詳細なコメントを追記してください。コード自体は変更しないでください。」
これにより、ブラックボックスだったコードが「説明付きのブラックボックス」になります。これだけでも、将来の保守担当者にとっては大きな救いとなります。
コードが読みやすくなると、不思議と「直せそう」な気がしてきませんか? 次は、少し踏み込んでコードの構造自体を整理していきましょう。
実践ステップ2:ネスト地獄からの脱出「構造の単純化」
可読性の向上でAIとの連携に慣れてきたら、いよいよコードの構造にメスを入れます。ここでターゲットにするのは、通称「波動拳コード」と呼ばれる、インデントが深くなりすぎたネスト地獄です。
ガード節(Guard Clause)による早期リターン
深いネストの主な原因は、if 文の中に if 文が重なり、さらに else が続く構造です。これを解消する定石が「ガード節」です。例外的な条件や不正な値を関数の先頭で弾き(return)、メインの処理をネストの外に出す手法です。
修正前のネスト地獄(TypeScript):
function processUser(user: User | null) {
if (user !== null) {
if (user.isActive) {
if (user.hasSubscription) {
// メインの処理
saveData(user);
} else {
throw new Error("No subscription");
}
} else {
throw new Error("User is not active");
} else {
throw new Error("User is null");
}
}
}
これを人間が手で直すと、括弧の閉じ忘れやスコープのミスが発生しがちです。AIに任せてみましょう。
プロンプト:
「以下のコードを、ガード節(Guard Clauses)を用いて早期リターンする形にリファクタリングしてください。ネストの深さを最小限に抑え、ロジックの等価性を維持してください。」
AIによる改善例:
function processUser(user: User | null) {
if (user === null) {
throw new Error("User is null");
}
if (!user.isActive) {
throw new Error("User is not active");
}
if (!user.hasSubscription) {
throw new Error("No subscription");
}
// メインの処理
saveData(user);
}
驚くほどスッキリしましたね。読む際の脳内メモリの負担が激減します。
Chain of Thought(思考の連鎖)プロンプトの活用
さらに複雑なロジックを変更する場合、AIにいきなり答えを出させるのではなく、「思考の過程」を出力させることで、論理破綻を防ぐことができます。これを「Chain of Thought」と呼びます。
プロンプト:
「この関数を責務ごとに分割してリファクタリングしたいと考えています。
- まず、現在のコードの処理フローをステップごとに分析してください。
- 次に、どの部分を独立した関数として切り出せるか検討し、その理由を述べてください。
- 最後に、分割後のコードを提示してください。」
このように段階を踏ませることで、AIは「まず理解し、次に設計し、最後に実装する」というエンジニアと同じ思考プロセスを辿ります。結果として、文脈を無視した突飛な分割案が出てくるリスクを減らせます。
しかし、どんなに慎重にプロンプトを設計しても、AIがミスをする可能性はゼロではありません。最後に、リファクタリングを安全に完了させるための「命綱」についてお話しします。
安全装置:AIの「幻覚」を防ぐためのテスト戦略
ここまでAIの有用性を語ってきましたが、忘れてはならない事実があります。それは、先ほども触れた通り「AIは平気で嘘をつく可能性がある」ということです。特に、使用しているライブラリのバージョンには存在しないメソッドを提案してくるケースは珍しくありません。
リファクタリングにおける命綱は「テストコード」です。しかし、レガシーコードにはテストがないことがほとんどでしょう。ここでもAIを活用します。
リファクタリング前の単体テスト作成もAIに頼む
コードをいじる前に、まず現在の挙動を保証するテストを書きます。これを人間がやるのは骨が折れますが、AIなら一瞬です。
プロンプト:
「以下のレガシーコードの挙動を担保するための単体テストコード(Jest使用)を作成してください。
正常系だけでなく、境界値や異常系のテストケースも網羅的に含めてください。
このテストは、現在のコードでPassすることを前提とします。」
まずこのテストを実行し、全てグリーン(合格)になることを確認します。これが「現在の仕様」です。
修正前後の挙動一致を確認するプロセス
安全なAIリファクタリングのワークフローは以下のようになります。
- 現状のテスト生成: AIにテストを書かせ、Passすることを確認。
- リファクタリング: AIにコード修正を依頼。
- テスト実行: 修正後のコードに対して、1で作ったテストを実行。
- 検証: テストが通れば、ロジックは壊れていない(機能的等価性が保たれている)と判断。
もしテストが落ちたら? それはAIがロジックを変えてしまったか、元のコードにあったバグを「修正」してしまったかのどちらかです。いずれにせよ、テストがあることで「何かが変わった」ことに即座に気づけます。
人間の目による最終レビューのポイント
テストが通ったからといって、盲信は禁物です。最後に必ず人間の目で以下の点をチェックしてください。
- 意図しない仕様変更がないか: 「バグだと思って直しておきました」とAIが気を利かせることがありますが、レガシーシステムでは「バグも仕様のうち」という場合があります。
- セキュリティリスク: SQLインジェクションの脆弱性などが新たに埋め込まれていないか。最近の研究では、AI生成コードに脆弱性が含まれるケースも報告されています。
- 可読性: AIが生成したコードが、チームのコーディング規約に合っているか。
まとめ
AIを活用したリファクタリングは、決して「楽をするため」だけのものではありません。それは、これまで恐怖の対象でしかなかったレガシーコードと向き合い、再びコントロールを取り戻すための勇気ある一歩です。
今回紹介したステップを振り返りましょう。
- マインドセット: 丸投げせず、ペアプログラミングとして取り組む。
- プロンプト: 役割・制約・形式を定義した「型」を使う。
- 可読性向上: 変数名やコメントなど、ロジックを変えない安全な改善から始める。
- 構造単純化: ガード節などでネストを解消する。
- テスト戦略: AIにテストを書かせ、安全網を張ってから修正する。
まずは、あなたのプロジェクトの中で「一番触りたくないファイル」を開いてみてください。そして、AIに「このコード、どう思う?」と問いかけることから始めてみませんか? きっと、今まで見えなかった解決の糸口が見えてくるはずです。
コメント