コードリファクタリングの神器:AIに古いコードを最適化させる

ども、松山です。
すべての開発者が経験したことのある悪夢があります:

3年前のプロジェクトを引き継いだら、コードは汚くて長く、ドキュメントもなく、書いた本人はすでに退職済み。あなたのミッション:リファクタリングすること

従来の方法:数週間かけてコードを読み、ロジックを理解し、バグを入れないように慎重に修正…

Claude Codeを使うと:AIをリファクタリングパートナーにして、効率が5~10倍向上します。

今日は、完全な実例を通して、AIで古いコードをリファクタリングする方法を教えます。

なぜリファクタリングにAIが必要?

リファクタリングの3つの難点

難点1:古いコードの理解

  • ドキュメント不足
  • ビジネスロジックが複雑
  • コードスタイルがバラバラ

難点2:問題の特定

  • パフォーマンスのボトルネックはどこ?
  • どのコードに隠れたバグがある?
  • どの部分がベストプラクティスに反している?

難点3:安全なリファクタリング

  • 変更はどこに影響する?
  • 機能が変わらないことをどう保証する?
  • リファクタリング成功をどう検証する?

AIの強み:

  • コード構造を素早く理解
  • 体系的に問題を発見
  • 改善案を提案
  • テストケースを生成

リファクタリングフロー:段階的に進める

第一段階:コード健診

目標: コード品質を全面的に把握

あなた:legacy/data_processor.pyをリファクタリングしたいです。
まず全面的なコードレビューをして、以下を教えてください:
1. コードの主な機能
2. コード品質スコア(1-10点)
3. 主な問題リスト(優先度順)
4. 推奨されるリファクタリング戦略

Claude:data_processor.pyを分析しました(180行のコード)

【機能概要】
CSVファイルから販売データを読み取り、クレンジング後にSQLiteデータベースに保存

【コード品質:4/10点】

【主な問題】(深刻度順)

🔴 深刻な問題:
1. SQLインジェクションのリスク(67行目):文字列結合でSQLを構築
2. メモリリークのリスク(45行目):ファイル全体を一度に読み込み
3. ハードコードされたパス(12行目):ファイルパスが固定値

🟡 中程度の問題:
4. エラー処理がない:try-exceptがなく、エラーでクラッシュ
5. 廃止されたAPIを使用:pandas.append()は1.4.0で非推奨
6. 型アノテーションなし:関数パラメータと戻り値に型がない

🟢 軽微な問題:
7. コードの重複:データクレンジングロジックが3回重複
8. 不明確な命名:変数名がdf1、df2、temp...
9. ドキュメント不足:docstringがない
10. ユニットテストがない

【推奨戦略】
3段階での進行を推奨:
1. まず深刻な問題を修正(セキュリティとパフォーマンス)
2. 次にコード品質を改善(エラー処理、API更新)
3. 最後に可読性を最適化(命名、ドキュメント)

各段階で:
- テストを書いて機能を検証
- 小さくコミット
- 段階的に推進

価値:

  • 明確な問題リスト
  • 合理的な優先順位
  • 実行可能な計画

第二段階:セーフティネットを構築(テスト)

リファクタリング前にまずテストを書く!

あなた:素晴らしい分析ですね!リファクタリングを始める前に、
既存コードのユニットテストを書いて、
主要機能をカバーしてください。これでリファクタリング時に機能が変わっていないか検証できます。

Claude:了解しました、まずテストで保護を構築します。以下を行います:
1. 既存関数の入出力を分析
2. テストデータを準備
3. テストケースを作成
4. テストを実行して現在のコードが通過することを確認

tests/test_data_processor.pyを作成中…

[テストコードを表示]

テスト例:

# tests/test_data_processor.py
import pytest
from legacy.data_processor import process_csv, clean_data

def test_process_csv_basic():
    """基本的なCSV処理をテスト"""
    result = process_csv('test_data/sample.csv')
    assert len(result) == 100
    assert 'sales' in result.columns

def test_clean_data():
    """データクレンジングをテスト"""
    dirty_data = pd.DataFrame({
        'sales': [100, -50, None, 200]
    })
    clean = clean_data(dirty_data)
    assert len(clean) == 2  # 負の値とnull値はフィルタされるはず
Claude:✓ 12個のテストケースを作成
✓ カバレッジ:85%
✓ すべてのテストが合格

安全にリファクタリングを始められます!

第三段階:深刻な問題を修正

セキュリティとパフォーマンスの問題を優先処理

あなた:リファクタリングを開始してください。まず3つの深刻な問題を解決:
1. SQLインジェクション
2. メモリ問題
3. ハードコードされたパス

1つ修正するごとに、テストを実行して機能が正常か確認してください。

Claude:1つ目の問題を修正します:SQLインジェクション

[変更を表示]
--- a/legacy/data_processor.py
+++ b/legacy/data_processor.py
-    sql = f"INSERT INTO sales VALUES ('{name}', {amount})"
-    cursor.execute(sql)
+    sql = "INSERT INTO sales VALUES (?, ?)"
+    cursor.execute(sql, (name, amount))

変更の説明:
- 文字列結合をパラメータ化クエリに置き換え
- SQLインジェクション攻撃を防止

テストを実行… ✓ すべて合格

2つ目の問題に続きますか?
あなた:はい、続けてください

Claude:2つ目の問題を修正:メモリ使用

[変更を表示]
-    df = pd.read_csv(file_path)  # 一度に読み込み
+    # チャンクで読み込み、毎回10000行を処理
+    for chunk in pd.read_csv(file_path, chunksize=10000):
+        process_chunk(chunk)

変更の説明:
- chunksizeでチャンク読み込み
- 大きなファイル(GB級)の処理に適している

テストを実行… ✓ すべて合格

3つ目の問題に続きますか?
あなた:続けてください

Claude:3つ目の問題を修正:ハードコードされたパス

[変更を表示]
+import os
+
+DEFAULT_DATA_PATH = os.getenv('DATA_PATH', './data')
+
-def process_csv():
-    file_path = '/home/user/data/sales.csv'
+def process_csv(file_path: str = None):
+    if file_path is None:
+        file_path = os.path.join(DEFAULT_DATA_PATH, 'sales.csv')

変更の説明:
- パスを関数パラメータに変更
- 環境変数での設定をサポート
- デフォルト値を提供

テストを実行… ✓ すべて合格

深刻な問題はすべて修正されました!この変更をコミットしますか?

第四段階:コード品質の改善

あなた:素晴らしい!変更をコミットしてください。次は中程度の問題を処理:
エラー処理の追加、廃止されたAPIの更新、型アノテーションの追加

Claude:コード品質の改善を開始…

【エラー処理の追加】
[変更を表示]
+import logging
+
+logger = logging.getLogger(__name__)
+
 def process_csv(file_path: str = None):
+    try:
         data = pd.read_csv(file_path)
+    except FileNotFoundError:
+        logger.error(f"File not found: {file_path}")
+        raise
+    except pd.errors.EmptyDataError:
+        logger.warning(f"Empty file: {file_path}")
+        return pd.DataFrame()
+    except Exception as e:
+        logger.error(f"Error processing {file_path}: {e}")
+        raise

【廃止されたAPIの更新】
-    result = result.append(new_row, ignore_index=True)
+    result = pd.concat([result, pd.DataFrame([new_row])], ignore_index=True)

【型アノテーションの追加】
from typing import Optional
import pandas as pd

-def process_csv(file_path=None):
+def process_csv(file_path: Optional[str] = None) -> pd.DataFrame:
    """CSVファイルを処理してクレンジング済みデータを返す

    Args:
        file_path: CSVファイルパス、デフォルトは環境変数DATA_PATHを使用

    Returns:
        クレンジング済みのDataFrame

    Raises:
        FileNotFoundError: ファイルが存在しない
    """

テストを実行… ✓ すべて合格
型チェック(mypy)を実行… ✓ 合格

第五段階:可読性の最適化

あなた:完璧です!最後のステップ:命名とコード構造の最適化

Claude:可読性の最適化を開始…

【命名の改善】
- df1 → sales_data
- df2 → cleaned_data
- temp → intermediate_result
- func1 → validate_sales_amount

【重複コードの抽出】
以前:データクレンジングロジックが3回重複

現在:
def clean_sales_data(data: pd.DataFrame) -> pd.DataFrame:
    """統一されたデータクレンジング関数"""
    data = remove_negative_values(data)
    data = fill_missing_values(data)
    data = remove_outliers(data)
    return data

3箇所でこの関数を再利用

【ドキュメントの追加】
+## データ処理フロー
+1. CSVファイルを読み込み(大きなファイルはチャンク読み込み対応)
+2. データ検証とクレンジング
+3. SQLiteデータベースに保存
+
+## 使用例
+```python
+from data_processor import process_csv
+process_csv('sales.csv')
+```

テストを実行… ✓ すべて合格
コード可読性スコア:8.5/10(元:4/10)

第六段階:パフォーマンス最適化

あなた:コード品質はすでに良くなりました。
パフォーマンスを分析して、さらに最適化の余地がありますか?

Claude:パフォーマンス分析を実行中…

【パフォーマンステスト結果】
テストファイル:100MB、100万行のデータ

リファクタリング前:45.3秒
リファクタリング後:8.7秒
向上:5.2倍 ⚡

【ボトルネック分析】
主な時間消費:
- データベース挿入:65%(5.7秒)
- データクレンジング:25%(2.2秒)
- ファイル読み込み:10%(0.8秒)

【最適化提案】
データベース挿入をさらに最適化できます:

1. 逐行挿入をバッチ挿入に置き換え
2. トランザクションを使用

改善コード:
-for row in data.iterrows():
-    cursor.execute("INSERT INTO sales VALUES (?, ?)", row)
+# バッチ挿入、毎回1000行
+cursor.executemany(
+    "INSERT INTO sales VALUES (?, ?)",
+    data.values.tolist()
+)

予想される向上:さらに3-4倍高速化

この最適化を実施しますか?
あなた:はい、実施してください

Claude:[最適化を実施]
パフォーマンステストを実行…
新しい時間:2.1秒
全体的な向上:21.6倍! ⚡⚡⚡

実戦ケース:レガシーPythonプロジェクトのリファクタリング

プロジェクト背景

状況:

  • プロジェクト:ECデータ分析スクリプト
  • 規模:500行のコード
  • 年齢:3年
  • 問題:遅い、不安定、保守が難しい

完全なリファクタリングプロセス

1. 全面評価

あなた:legacy/ディレクトリ全体を分析して、
リファクタリングが必要なファイルをすべて見つけ、
優先度順に並べてください

Claude:スキャン完了、5つのファイルがリファクタリング必要です:

高優先度(深刻な問題):
1. data_processor.py - 品質:3/10(SQLインジェクション、パフォーマンス悪い)
2. api_client.py - 品質:4/10(エラー処理なし、ハードコード)

中優先度(品質問題):
3. report_generator.py - 品質:5/10(コード重複、命名悪い)
4. utils.py - 品質:6/10(型アノテーションなし、ドキュメントなし)

低優先度(小さな問題):
5. config.py - 品質:7/10(構造が改善可能)

推奨:data_processor.pyから始めて、順番に処理

2. ファイルごとにリファクタリング

あなた:了解しました、この順番で進めてください。
各ファイル完了後:
1. テストを実行
2. gitにコミット
3. 次に進む

Claude:了解しました、data_processor.pyのリファクタリングを開始…
[上記の完全なフローに従う]

✓ data_processor.py リファクタリング完了
✓ テスト合格
✓ コミット済み(commit: refactor: improve data_processor.py)

api_client.pyを続けます…

3. 全体最適化

あなた:すべてのファイルのリファクタリングが完了したら、
ファイル間の依存関係をチェックして、
最適化できるアーキテクチャ上の問題がないか確認してください

Claude:プロジェクトアーキテクチャを分析中…

発見された問題:
1. 循環依存:data_processor ↔ report_generator
2. 重複コード:3つのファイルに類似のログ設定がある
3. 結合度が高い:api_clientが直接データベースにアクセス

推奨されるリファクタリング:
1. 循環依存を解消、インターフェース層を導入
2. 共通のログ設定をconfig.pyに抽出
3. リポジトリ層を追加して疎結合化

[リファクタリング案を表示]

これらの改善を実施しますか?

リファクタリング成果の比較

コード品質の向上:

指標 リファクタリング前 リファクタリング後 向上
平均品質スコア 4.2/10 8.5/10 +102%
テストカバレッジ 0% 87% +87%
コード行数 500行 620行 +24%*
ドキュメント化率 5% 95% +90%

*コードが増えた理由:エラー処理、型アノテーション、ドキュメントを追加

パフォーマンス向上:

タスク リファクタリング前 リファクタリング後 向上
100万件のデータ処理 45秒 2秒 22.5倍
レポート生成 12秒 1.5秒 8倍
APIリクエスト(100回) 30秒 8秒 3.75倍

安定性向上:

  • リファクタリング前:毎週2-3回クラッシュ
  • リファクタリング後:3ヶ月間ゼロクラッシュ

リファクタリングのベストプラクティス

原則1:小さく速く進む

❌ やってはいけない:

システム全体を一度に書き直す

✅ やるべき:

毎回1つの問題だけを変更
テスト合格後すぐコミット
段階的に推進

原則2:テスト優先

フロー:

1. テストを書く(既存機能をカバー)
2. テストが合格することを確認
3. コードをリファクタリング
4. テストがまだ合格することを確認
5. コミット

原則3:機能を変えない

リファクタリングの定義:

コード構造を変える
外部動作は変えない

原則4:優先順位を明確に

順序:

1. セキュリティ問題(SQLインジェクション、XSSなど)
2. パフォーマンス問題(深刻なボトルネック)
3. 安定性問題(クラッシュ、メモリリーク)
4. 保守性問題(コード品質)
5. 美観問題(命名、フォーマット)

よくあるリファクタリングシーン

シーン1:長い関数の分割

問題: 200行の巨大関数

あなた:process_order関数が200行あって、長すぎます。
複数の小さな関数に分割してください。各関数は1つのことだけを行うようにしてください。

Claude:関数構造を分析中…

6つのことをしています:
1. 注文データの検証
2. 在庫チェック
3. 価格計算
4. 注文作成
5. 在庫減算
6. 通知送信

リファクタリング案:6つの関数に分割
[分割後のコードを表示]

各関数は30行以内で、責任が単一

シーン2:コード重複の排除

問題: 類似コードが複数回出現

あなた:parse_json、parse_xml、parse_yamlの
3つの関数に重複コードがたくさんあります。
共通部分を抽出できますか?

Claude:重複パターンを分析中…

重複部分:
- ファイル読み込み
- エラー処理
- ログ記録
- 結果検証

リファクタリング案:
def parse_file(file_path, parser_func):
    """汎用パース関数"""
    data = read_file_with_retry(file_path)
    try:
        result = parser_func(data)
        validate_result(result)
        return result
    except Exception as e:
        log_error(e)
        raise

# 3つの関数を簡素化:
parse_json = lambda path: parse_file(path, json.loads)
parse_xml = lambda path: parse_file(path, xml_parser)
parse_yaml = lambda path: parse_file(path, yaml.safe_load)

コードが120行から40行に削減

シーン3:命名の改善

あなた:コード内の変数名が乱雑です。意味のある名前に変更してください。

Claude:分析して名前を変更中…

変更:
- data → customer_orders
- temp → filtered_results
- func1 → calculate_total_price
- x → discount_rate
- arr → product_ids

テストを実行… ✓ 合格
可読性が明らかに向上

次回予告

次の記事『Bugターミネーター:AIアシストデバッグの効率的な方法』では、以下を教えます:

  • AIに素早くバグを特定させる方法
  • エラーログとスタックトレースの分析
  • 複雑な本番環境の問題解決
  • 実戦:厄介な並行処理バグの解決

リファクタリングでコードを改善し、デバッグで問題を減らす。次回もお楽しみに!


💬 インタラクションタイム

今日の内容を読んで、最もリファクタリングしたい古いコードは何ですか?

  • A. 自分が書いた古いコード
  • B. 元同僚が残した「遺産」
  • C. オープンソースプロジェクトの技術的負債
  • D. 本番環境のパフォーマンスボトルネック

小さな課題: 古いコードファイルを1つ選んで、Claude Codeにコードレビューをさせて、どんな問題があるか見てみましょう!

このブログをフォローして、すべての記事が実践的です! 🚀


このシリーズについて

これは「AI駆動開発実践」シリーズの第5篇、実戦篇の第1篇です。今日はリファクタリングを学びました。次回はAIでデバッグします!

実際にリファクタリングを試してみてください。次回もお楽しみに! 👋

Last modified: 2025-11-13

Author