「このコード、誰が書いたんだ…?」
画面に映るスパゲッティコードを見つめながら、深いため息をついたことはありませんか? 変数名は tmp や data ばかり、関数は数百行に及び、コメントは数年前に更新が止まっている。そして何より恐ろしいのは、テストコードが存在しないことです。
「リファクタリングしたい。でも、触って壊したらどうしよう」
この恐怖心こそが、技術的負債を放置させてしまう最大の原因です。最近ではGitHub CopilotやChatGPTといったAIコーディングツールが急速に進化しています。特に最新の環境では、エージェントモードによる自律的なコード編集や、長大なプロジェクト全体の文脈を理解する高度な推論機能が搭載され、「AIに丸投げして直させればいい」という声も聞かれるようになりました。しかし、現場で指揮を執る立場であれば、次のような懸念を抱くのではないでしょうか。
「AIが勝手にコードを書き換えて、微妙なバグを埋め込んだらどうするんだ?」と。
その懸念は、システム開発の現場において極めて現実的で正しい視点です。AIは優秀であり、現在ではカスタムインストラクション(.github/copilot-instructions.mdなど)を活用してプロジェクト固有のコーディング規約を事前に学習させることも可能になりました。それでも、テストという安全網がない状態でAIに「破壊的な変更」を行わせるのは、依然として大きなリスクを伴います。旧来の単純なコード補完から、エージェントを活用した高度なワークフローへと移行しつつある今だからこそ、ツールの進化に合わせて私たちのアプローチも変える必要があります。
この記事では、技術的負債に立ち向かう開発チームに向けて、「AIを暴走させずに、安全にコードを蘇らせるリファクタリング術」を提案します。キーワードは「守りのリファクタリング」です。いきなりAIのエージェント機能を使って本番コードを修正させるのではなく、まずはAIの高度な文脈理解力を活かして「最強の盾(テストコード)」を作らせることから始めます。
この学習パスについて:AIを「暴走させない」リファクタリング術
多くのエンジニアがAIリファクタリングで失敗するのは、「速さ」を求めていきなり「最適化」を指示してしまうからです。本コースでは、速さよりも「安全性」と「確実性」を最優先します。
なぜAIリファクタリングに「恐怖」を感じるのか
人間が書いた複雑なロジックには、ドキュメント化されていない「暗黙の仕様」や「歴史的経緯」が隠れていることが多いものです。AIは表層的なロジックは理解できても、その裏にある業務的な文脈までは完全には読み取れません。
AIに「このコードをきれいにして」と丸投げするのは、新人のアルバイトに「店をいい感じに改装しておいて」と鍵を渡すようなものです。壁を取り払ったら、そこは建物を支える耐力壁だった、なんてことになりかねません。
本コースのゴール:AIを信頼できる「後輩」に育てる
この学習パスでは、AIを「勝手に工事をする業者」ではなく、「入念に準備を手伝ってくれる几帳面な後輩」として扱います。
- 理解させる: 既存コードの仕様を言語化させる
- 守らせる: テストコードを書かせて安全網を作る
- 整えさせる: ロジックを変えずに可読性だけ上げる
- 改善させる: 最後に初めてロジックを最適化する
この順序を守るだけで、リファクタリングの心理的ハードルは劇的に下がります。もし失敗しても、Gitで戻せばいいだけです。そして何より、ステップ1で作ったテストコードが、変更の正当性を証明してくれます。
所要時間と推奨ペース
まずは、手元のプロジェクトにある「あまり重要ではないが、少し読みづらいユーティリティ関数」1つから始めてみてください。所要時間は30分程度です。いきなりコアロジックに手を出すのは避け、小さな成功体験を積み重ねるのが現実的なアプローチです。
前提知識と準備:安全装置(セーフティネット)を用意する
作業を始める前に、転落防止のネットを張りましょう。これがあるだけで、心理的な負担を減らし、安心して作業に取り組めます。
必要なツールセット
- VS Code: エディタは使い慣れたもので構いませんが、AI拡張機能が豊富で統合が進んでいるVS Codeを推奨します。最新の環境では、インライン提案からチャット、エージェント機能までがCopilot Chat拡張に一本化されており、シームレスな操作が可能です。
- GitHub Copilot / ChatGPT / Claude: コードの生成と対話に使用します。特にClaudeやChatGPTは、複雑なコードの文脈を理解する能力が高く、リファクタリングの強力なパートナーとなります。
- ※公式ドキュメントで推奨されている通り、タスクの複雑さに応じてモデルを選択することが重要です。簡単な修正には軽量なモデルを、複雑なリファクタリングやテスト生成には最も高性能なモデルを割り当てるなど、状況に合わせた使い分けが費用対効果の面でも効果的です。
- カスタムインストラクション(GitHub Copilot利用時): プロジェクトのルートに
.github/copilot-instructions.mdを作成し、コーディング規約や「テストには必ずpytestを使用する」といったルールを記述しておくことをお勧めします。これにより、AIがプロジェクト固有のルールを自動で参照し、手戻りを大幅に減らせます。 - pytest: Pythonの標準的なテストフレームワークです。
unittestよりも記述が簡潔で、AIも得意としています。
リファクタリング前の鉄則「バージョン管理」
基本的なことですが、必ず新しいブランチを切ってください。ブランチ名は refactor/target-module-name など、目的が明確なものが適切です。
# 必ず作業前に実行
git checkout -b refactor/user-authentication-cleanup
「いつでも main ブランチの状態に戻れる」という確信が、AIに対する疑心暗鬼を和らげてくれます。
環境構築:サンドボックスの用意
リファクタリング対象のコードが、データベースや外部APIに依存している場合、そのままでは手軽にテストを実行できません。AIに依頼する前に、依存関係を切り離せるか確認するか、あるいは docker-compose などでローカルに閉じた実行環境(サンドボックス)を用意しましょう。
また、AIに的確なテストコードを生成させるためには、事前の準備として詳細なコメントを残すことも有効な手段です。曖昧な指示ではなく、具体的な処理内容やデータの依存関係をコード周辺に明記しておくことで、AIが正確なコンテキストを把握しやすくなります。
準備ができたら、AIというパートナーに向き合います。
ステップ1:命綱を張る(テストコードのAI生成)
ここが本記事のハイライトです。多くの人は「コードをきれいにする」ことから始めますが、まずは「テストを書く」ことから始めます。なぜなら、レガシーコードにはテストがないことがほとんどだからです。
「テストがないコード」はリファクタリングできない
マーティン・ファウラーの名著『リファクタリング』にもある通り、テストのないコード変更はリファクタリングではなく、ただの「危険な賭け」です。しかし、スパゲッティコードのテストを書くのは、コードを読むのと同じくらい苦痛を伴います。
そこでAIの出番です。AIは「既存コードの挙動をそのままテストに落とし込む」作業を非常に得意としています。
AIに既存コードの挙動を解析させる
まずは、対象のPythonコードをAIに解析させ、「仕様書」を作らせます。
従来はChatGPTやClaudeなどのWebブラウザ画面にコードを貼り付ける手法が主流でしたが、最新の開発環境ではエディタ統合型のAIアシスタント(GitHub Copilot Chatなど)の活用が効率的です。例えばVS Code環境では、AI機能がチャット拡張機能に一本化されており、エディタ内で直接コンテキストを渡して解析を依頼できます。
【プロンプト例:仕様理解】
あなたは熟練したPythonエンジニアです。
以下のコードは、ドキュメントが存在しないレガシーコードです。
このコードの挙動を解析し、以下の項目を出力してください。
- 関数の目的(1行で)
- 引数と戻り値の仕様
- 処理のフロー箇条書き
- 想定されるエッジケース(境界値や異常系)
[ここにコードを貼り付け、またはエディタ上で対象コードを選択]
AIが出力した「仕様」を読むことで、「こういう意図だったのか」という発見があるはずです。もしAIの解釈が間違っていたら、それはコードが人間にとってもAIにとっても分かりにくすぎる証拠と言えます。
実践:pytest用のテストケースを自動生成する
次に、その理解に基づいてテストコードを生成させます。ここでのポイントは、「現在の挙動(バグも含めて)を正とする」ことです。これを「仕様化テスト(Characterization Test)」と呼びます。まずは今の動きを固定することが目的だからです。エディタ内のAIエージェントに指示を出せば、現在のファイルを読み込んだ上で適切なテストコードを提案してくれます。
【プロンプト例:テスト生成】
ありがとう。では、このコードの現在の挙動を保証するためのテストコードを
pytestで作成してください。
以下の条件を守ってください。
- 正常系だけでなく、先ほど挙げたエッジケースも網羅すること。
- 外部依存(DBやAPI)がある場合は
unittest.mockを使ってモック化すること。- テストケースごとの説明をコメントで記述すること。
- Parameterized test(
@pytest.mark.parametrize)を活用して網羅性を高めること。
【生成されるコードのイメージ】
import pytest
from legacy_module import calculate_price
@pytest.mark.parametrize("price, tax_rate, expected", [
(100, 0.1, 110), # 正常系
(0, 0.1, 0), # 境界値:価格0
(100, 0, 100), # 境界値:税率0
(-100, 0.1, -110), # 異常系?:マイナス価格(現状の挙動通り)
])
def test_calculate_price(price, tax_rate, expected):
assert calculate_price(price, tax_rate) == expected
カバレッジの確認と安心感の獲得
生成されたテストを実行してみます。
pytest test_legacy_module.py
もしテストが落ちたら、AIの解釈かコードの挙動のどちらかが予想外だったということです。修正して、「全てのテストが通る(Green)」状態を作ってください。
これで、開発環境に命綱が巻かれました。これからどんな変更を加えても、このテストを実行してGreenであれば、機能は壊れていないと判断できます。この安心感こそが、リファクタリングを前に進めるための強力な基盤になります。
ステップ2:ノイズを除去する(可読性の向上)
命綱ができたら、少しずつコードを整理していきます。まだロジック(ifやforの構造)は変えません。見た目を整えるだけです。
ロジックを変えずに「見た目」だけ整える
レガシーコードが読みにくい最大の要因は、不適切な変数名や、型情報の欠落です。これらを修正しても、コンパイル後の動作(Pythonの場合はバイトコードの挙動)は変わりません。最も安全な変更です。
AIによる変数名・関数名の改善提案
【プロンプト例:リネーム】
以下の関数内の変数名は一般的すぎて意味が不明確です(例:
x,data,res)。
ロジックは一切変更せず、変数の名前だけを、文脈に沿った具体的な名前に変更してください。
Type Hints(型ヒント)も追加してください。
Before:
def calc(d, r):
res = d * (1 + r)
return res
After (AI生成):
def calculate_total_price(base_price: float, tax_rate: float) -> float:
total_amount = base_price * (1 + tax_rate)
return total_amount
これだけで、コードの意図が明確になります。変更したら、すかさず pytest を実行します。Greenならコミットします。
型ヒント(Type Hints)の自動付与
Python 3.5以降で導入された型ヒントは、AIツールと非常に相性が良いです。AIに型を推論させて付与することで、mypy などの静的解析ツールが使えるようになります。これも将来的なバグを防ぐ強力な武器になります。
docstringの生成とコード理解の深化
最後に、関数やクラスにドキュメンテーション文字列(docstring)を追加させます。「この関数は何をするものか」が自然言語で書かれているだけで、チームメンバーの理解度は格段に向上します。
ステップ3:複雑性を解きほぐす(関数レベルの最適化)
可読性が上がり、テストで守られている状態になりました。いよいよ、複雑に絡み合ったロジックを解きほぐします。
ネストの深いif文を「ガード節」で平坦化する
深いネストはバグの温床です。AIに「ガード節(Guard Clauses)を使ってネストを浅くして」と依頼しましょう。
【プロンプト例:ガード節】
以下のコードのネストが深くて読みにくいです。
ガード節(早期リターン)を使用して、ネストを浅くしてください。
ロジックの挙動は変更しないでください。
Before:
def process_order(order):
if order:
if order.is_paid:
if not order.is_shipped:
ship(order)
After (AI生成):
def process_order(order):
if not order:
return
if not order.is_paid:
return
if order.is_shipped:
return
ship(order)
変更後は必ずテストを実行します。もしここでテストが失敗したら、AIがロジックを微妙に変えてしまった可能性があります。すぐに git checkout で戻せるので、リスクは最小限に抑えられます。
長い関数をAIと分割する(Extract Method)
50行を超えるような長い関数は、意味のある単位で小さな関数に切り出すべきです。AIに「この部分を別の関数として切り出して」と指示します。
ここでのコツは、一度に全部やらせないことです。50行の関数を一気に5つの小関数に分けるのではなく、「まずは前半のバリデーション処理だけ切り出して」と指示します。スモールステップで進めることが安全の鍵です。
重複コード(DRY原則違反)の検出と統合
「この処理、他の場所でも見たな…」と思ったら、AIに確認してみましょう。
「ファイルAとファイルBに似たようなロジックがある気がする。比較して共通化できるか検討して」
AIはパターンマッチングが得意なので、人間が見落とすような重複も見つけてくれます。
ステップ4:実務への適用とチーム展開
個人の環境できれいになったコードを、チームの資産として統合します。
プルリクエスト(PR)作成時のAI活用
リファクタリングのPRは「何が変わったのかわからないが、行数が多い」と敬遠されがちです。ここでもAIを活用します。
【プロンプト例:PR作成】
以下の
git diffの内容をもとに、プルリクエストの説明文を作成してください。
変更の目的(リファクタリング)、主な変更点(テスト追加、可読性向上、ネスト解消)、レビュアーに見てほしいポイントを明確にしてください。
AIが作成した説明文は論理的で分かりやすいため、レビュアーの負担を軽減できます。「テストを追加し、パスしていること」を強調すれば、マージの承認も得やすくなるでしょう。
チームメンバーへの説明責任を果たす
「なぜ今、リファクタリングが必要だったのか?」と問われたら、「AIを活用してテストカバレッジを0%から80%に引き上げました。これにより今後の機能追加が安全かつ迅速に行えます」と答えてください。数値(カバレッジ)と事実(テスト自動生成)に基づいた論理的な説明は、ビジネスサイドやマネージャーをも納得させる力があります。
「小さなリファクタリング」を習慣化する
ボーイスカウトには「来た時よりも美しく」というルールがあります。コードも同様です。巨大なリファクタリングプロジェクトを立ち上げる必要はありません。日々の修正のついでに、AIに「ここ、テスト書いて」「ここ、変数名わかりやすくして」と依頼するだけです。この小さな習慣の積み重ねが、中長期的な開発効率を劇的に向上させます。
よくある質問と挫折ポイント:AIの嘘を見抜く
最後に、AIリファクタリングで陥りがちな課題とその実践的な対処法を解説します。
Q. AIが誤ったコード(幻覚)を生成したら?
A. テストがそれを確実に検知します。そして、適切な指示出しで予防も可能です。
これが、ステップ1でテストを書いた最大の理由です。AIがもっともらしく誤ったコード(ハルシネーション)を出力しても、テストを実行すれば FAILED となります。テストさえあれば、AIの誤生成は「致命的なバグ」ではなく、単なる「試行錯誤のプロセス」に変わります。
さらに、最新のベストプラクティスを取り入れることで、誤生成自体を減らすことができます。例えば、GitHub Copilotなどのツールでは、カスタムインストラクション(プロジェクト固有のコーディング規約やルールの事前設定)を活用することが推奨されています。また、曖昧な指示を避け、具体的なデータモデルや処理フローをコメントで詳細にコンテキストとして提供することで、AIはより正確なコードを提案しやすくなります。
Q. セキュリティリスクはないのか?
A. 機密情報(APIキーやパスワード、個人情報)は絶対にプロンプトに入力しないでください。
コードそのものに機密情報が含まれている場合は、事前にダミー値へ置換してからAIに渡す運用を徹底することが重要です。高いセキュリティ要件が求められるプロジェクトでは、ローカル環境で動作するLlamaなどのオープンソースAIモデルの使用も現実的な選択肢となります。
また、クラウドサービスを利用する場合は、企業向けのプラン(ChatGPTのEnterpriseプランやGitHub Copilot Businessなど)を導入することで、入力データがAIの再学習に利用されないオプトアウト設定が適用されるのが一般的です。導入前には、必ず自社のセキュリティポリシーと各公式ドキュメントの最新のデータプライバシー条項を確認してください。
モチベーション維持:完璧を目指さない勇気
一度にすべてのコードを綺麗にする必要はありません。「頻繁に変更が入る箇所」や「バグが集中している複雑なモジュール」から着手するだけで、十分な費用対効果が得られます。
AIは疲れることなくコードを生成し続けますが、それをレビューし、品質を担保する人間のエンジニアには限界があります。作業の難易度に合わせてAIの利用モードを切り替えるのも賢いアプローチです。例えば、単純なテスト生成には軽量で高速なモデルを使い、複雑なアーキテクチャの分析やリファクタリングには高性能なモデルを割り当てるなど、ツールを計画的に使い分けることで負担を軽減できます。「今日はこの関数だけ」「明日はあのクラスだけ」と割り切り、持続可能なペースで進めることが、現場での定着を成功させる秘訣です。
まとめ:恐怖から自信へ
AIを活用したリファクタリングは、決して「AIにすべてを丸投げする」ことではありません。むしろ、「AIの力を借りて、人間が安心してコードを改善できる堅牢な環境を構築する」ための現実的なプロセスだと言えます。
- テスト自動生成で安全のための命綱を張る
- 可読性向上でシステム全体の視界をクリアにする
- 構造最適化で蓄積した技術的負債を確実に返済する
この3つの手順を順番に踏むことで、既存システムを「壊してしまうかもしれない」という漠然とした恐怖は、「確実なテストコードがあるから大丈夫」という論理的な自信へと変わります。AIは単なる自動化ツールではなく、エンジニアの心理的負担を軽減し、プロジェクトの品質を担保するための強力なパートナーとして機能します。
まずは手元のプロジェクトを開き、一番気がかりなあの関数をAIに提示してみてください。
「このコードのテストを書いて」
そのシンプルな一言が、チームの技術的負債を解消し、より健全で生産性の高い開発サイクルを取り戻すための確実な第一歩となります。
コメント