refactoring について
既存コードの外部動作を保ちながら再構成する手順を、AIエージェントに示すスキルです。コードを保守する開発者向けに、問題箇所の特定、変更計画、安全な適用、テストによる検証を支援します。
主な機能
- 01コードの問題点を特定
- 02リファクタリングパターンを選択
- 03小さな変更を順序立てて計画
- 04APIと動作を維持
- 05テストで変更を検証
- 06作業内容を要約
活用シーン
- 01長い関数や深いネストを分割
- 02重複ロジックを除去
- 03命名を改善し条件を簡潔化
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ seb1n/ awesome-ai-agent-skills --skill refactoringインストール後、Claude Code で/refactoringを入力して読み込まれたか確認します。
詳細
コードのリファクタリング
このスキルでは、外部から見える動作を変えずに既存のコードを再構成するための、規律あるプロセスを AI エージェントに案内します。リファクタリングによって可読性が向上し、複雑さが軽減され、コードベースの拡張や保守が容易になります。エージェントはコードの不吉な臭いを特定し、的を絞ったリファクタリングパターンを提案して、安全に変換を適用し、テストを通じて正しさを検証します。
ワークフロー
-
コードの不吉な臭いを特定する:対象のコードをスキャンして、よくある品質上の問題を探します。長すぎる関数、深くネストした条件分岐、重複したロジック、範囲が広すぎる変数、マジックナンバー、デッドコード、多すぎるパラメーターなどが該当します。各問題の場所と、それがコードベースに悪影響を及ぼす理由を簡潔に示します。
-
リファクタリングパターンを選択する:特定した問題ごとに、最も適切なリファクタリングパターンを選択します。一般的なパターンには、メソッドの抽出、シンボルの名前変更、条件分岐の簡素化、変数のインライン化、マジックナンバーの名前付き定数への置き換え、デッドコードの削除、パラメーターオブジェクトの導入などがあります。提案する変更ごとに、トレードオフと期待される改善を説明します。
-
変更の順序を計画する:リファクタリングを安全に適用できる順序を決めます。それぞれを個別に検証できる、小規模で独立した変更を優先します。関連する変更(たとえば、ヘルパーを抽出してから名前を変更する)をまとめ、ロールバックを難しくする無関係な変換の混在は避けます。
-
リファクタリングを適用する:一度に1つのパターンずつコードを変換します。元のパブリック API と動作を維持します。各言語に適した構文を使用します。Python ではリスト内包表記、JavaScript では分割代入、Rust ではパターンマッチングなどが該当します。
-
テストを実行して検証する:変換のたびに既存のテストスイートを実行します。テストがない場合は、変更前後のリファクタリング対象をカバーする軽量なユニットテストを作成します。すべてのテストが成功し、リグレッションが発生していないことを確認します。
-
変更を記録する:適用した各リファクタリング、その対象となった問題、今回可能になった追加の改善点を要約します。この要約は、コミットメッセージや PR の説明として使用できます。
対応言語
- Python
- JavaScript / TypeScript
- Java
- Go
- Rust
- C / C++
- Ruby
使い方
リファクタリングしたいコードを、任意の目標(「複雑さを減らす」「命名を改善する」「小さな関数に分割する」など)とともに提示してください。エージェントがコードを分析し、リファクタリング計画を提示して、承認後に変更を適用します。ファイルやモジュール全体を指定して、全般的な品質改善を依頼することもできます。
例
例 1 — メソッドの抽出と条件分岐の簡素化(Python)
ユーザーのリクエスト:「この関数は長すぎて、ネストした if 文が分かりにくいです。リファクタリングしてください。」
変更前:
def register_user(payload):
if payload.get("email"):
if "@" in payload["email"]:
if payload.get("password") and len(payload["password"]) >= 8:
user = {"email": payload["email"], "active": True}
db.save(user)
send_welcome_email(user["email"])
log.info(f"User {user['email']} registered")
return user
else:
raise ValueError("Password must be at least 8 characters")
else:
raise ValueError("Invalid email format")
else:
raise ValueError("Email is required")
変更後:
def register_user(payload):
_validate_payload(payload)
user = _create_user(payload["email"])
_notify_and_log(user)
return user
def _validate_payload(payload):
if not payload.get("email"):
raise ValueError("Email is required")
if "@" not in payload["email"]:
raise ValueError("Invalid email format")
if not payload.get("password") or len(payload["password"]) < 8:
raise ValueError("Password must be at least 8 characters")
def _create_user(email):
user = {"email": email, "active": True}
db.save(user)
return user
def _notify_and_log(user):
send_welcome_email(user["email"])
log.info(f"User {user['email']} registered")
適用したパターン:メソッドの抽出、ネストした条件分岐のフラット化(ガード節)。
例 2 — 重複の削除と定数の導入(TypeScript)
ユーザーのリクエスト:「この Express のルートハンドラーを整理してください。繰り返しが多すぎます。」
変更前:
app.post("/orders", async (req, res) => {
if (!req.body.items || req.body.items.length === 0) {
return res.status(400).json({ error: "Items are required" });
}
if (req.body.items.length > 50) {
return res.status(400).json({ error: "Too many items" });
}
let total = 0;
for (const item of req.body.items) {
total += item.price * item.quantity;
}
if (total > 10000) {
return res.status(400).json({ error: "Order exceeds maximum total" });
}
const order = { items: req.body.items, total: total, status: "pending" };
await db.orders.insert(order);
return res.status(201).json(order);
});
変更後:
const MAX_ITEMS = 50;
const MAX_ORDER_TOTAL = 10_000;
app.post("/orders", async (req, res) => {
const { items } = req.body;
const validationError = validateOrder(items);
if (validationError) {
return res.status(400).json({ error: validationError });
}
const total = calculateTotal(items);
if (total > MAX_ORDER_TOTAL) {
return res.status(400).json({ error: "Order exceeds maximum total" });
}
const order = await createOrder(items, total);
return res.status(201).json(order);
});
function validateOrder(items?: OrderItem[]): string | null {
if (!items || items.length === 0) return "Items are required";
if (items.length > MAX_ITEMS) return "Too many items";
return null;
}
function calculateTotal(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
async function createOrder(items: OrderItem[], total: number) {
const order = { items, total, status: "pending" as const };
await db.orders.insert(order);
return order;
}
適用したパターン:メソッドの抽出、マジックナンバーの名前付き定数への置き換え、分割代入。
ベストプラクティス
- リファクタリングは小さく、独立した単位で行います。 各変更は個別に検証でき、問題が起きた場合に簡単に元に戻せるようにします。
- リファクタリングと機能追加を同時に行わないでください。 動作の変更と構造の変更を混在させると、バグの追跡がほぼ不可能になります。
- 公開 API を維持します。 ユーザーが API の再設計を明示的に求めない限り、内部構造の変更によって呼び出し側に変更を強いるべきではありません。
- テストスイートを活用します。 テストカバレッジが低い場合は、リファクタリングの前に現行の動作を捉える特性テストを作成します。
- ガード節を使って、深くネストした条件分岐を整理します。 早期リターンは、ネストした分岐にコメントを追加するよりも、はるかに読みやすさを向上させます。
- 積極的に名前を変更します。 明確な名前を付ければ、コメントは不要になります。何をチェックするのかをコメントで説明した
checkよりも、validate_emailという関数名のほうが適切です。
エッジケース
- 既存のテストがない場合:変換を適用する前に、現在の入力と出力の動作を捉える最小限のテストを生成します。正しさへの確信はテストカバレッジに左右されることをユーザーに伝えます。
- 密結合したモジュール:あるモジュールのリファクタリングによって、別のモジュールのインポートや契約が壊れる場合は、まず依存関係グラフを作成し、ロジックを切り出す前にインターフェースの境界を提案します。
- 生成コードまたはベンダー提供コード:自動生成されたファイル(protobuf のスタブ、ORM のマイグレーションなど)はリファクタリングしないでください。該当するファイルを示し、対象から除外します。
- パフォーマンスが重要なホットパス:「見苦しい」コードでも、意図的に最適化されている場合があります。手作業で調整されたループを、パフォーマンスが低下する可能性のある高水準の抽象化に置き換える前に、ユーザーに確認します。
- 書式スタイルが混在している場合:ファイル内でスタイル(タブとスペース、引用符のスタイルなど)が一貫していない場合は、リファクタリング中に手作業で整えるのではなく、リファクタリング後にプロジェクトのフォーマッターを実行します。