45395 - シコウサクゴ -

AIにリファクタリングを任せる:循環的複雑度を60→19に下げた実践

2026-04-03
AI駆動開発
AI駆動開発
Claude Code
リファクタリング
循環的複雑度
コード品質
Last updated:2026-04-05
10 Minutes
1825 Words

「このメソッドの循環的複雑度(Cyclomatic Complexity)が60です」——人間がこの数字を見たら、リファクタリングに着手する気力が湧かないかもしれません。しかしClaude Codeに任せると、1〜2時間で60→19に下げてくれます。

本記事では、3つのデータ処理エンジン(A・B・C)のコアモジュールの複雑度を段階的に低減した実践と、AIにリファクタリングを依頼する際のコツを記録します。


循環的複雑度(CC)とは

循環的複雑度(Cyclomatic Complexity、CC)は、プログラムの制御フローの複雑さを数値化したものです。分岐(if/elif/else)やループ(for/while)が増えるほど値が大きくなります。

1
# CC = 1(分岐なし)
2
def add(a: int, b: int) -> int:
3
return a + b
4
5
# CC = 3(if + elif)
6
def classify(score: int) -> str:
7
if score >= 80:
8
return "A"
9
elif score >= 60:
10
return "B"
11
else:
12
return "C"
13
14
# CC = 15+(実際の本番コードから抜粋・簡略化)
15
def process_signal(signal, source_data, config, resources, ...):
7 collapsed lines
16
if signal.type == "EXECUTE":
17
if source_data.external_metric > 35:
18
if config.fallback_enabled:
19
if len(resources) < config.max_resources:
20
if signal.score > config.min_score:
21
if source_data.session == "BATCH_A":
22
# ... さらにネストが深くなる

一般的な目安:

CC評価
1〜10シンプル、テストしやすい
11〜20やや複雑、注意が必要
21〜50複雑、テストが困難
50以上テスト不能、リファクタリング必須

対象となった3つのモジュール

H-COMP-01: データ処理エンジンA 統合分析モジュール

1
ファイル: unified_analyzer.py
2
対象メソッド: _analyze_pair()
3
Before: CC = 60
4
After: CC = 19(-68%)
5
手法: 5メソッドに分割

_analyze_pair()は「1つのデータペアの全指標を計算する」メソッドでした。411指標の計算ロジックが1メソッドに詰め込まれており、if文とfor文が多重にネストしていました。

H-COMP-02: データ処理エンジンB

1
ファイル: processing_engine.py
2
対象メソッド: __init__() と _process_batch_breakout()
3
Before: __init__() CC = 53, _process_batch_breakout() CC = 51
4
After: __init__() CC = 21(-60%), _process_batch_breakout() CC = 6(-88%)
5
手法: 4メソッド分割、14サブメソッド化

__init__()にCC 53という数値は「初期化処理にビジネスロジックが混入している」サインです。

H-COMP-03: データ処理エンジンC

1
ファイル: processing_engine.py
2
対象メソッド: run_session() と _monitor_resources()
3
Before: run_session() CC = 42, _monitor_resources() CC = 24
4
After: run_session() CC = 23(-45%), _monitor_resources() CC = 10(-58%)
5
手法: 4メソッド分割、10サブメソッド化

AIへの依頼方法

基本プロンプト

1
unified_analyzer.py の _analyze_pair() メソッドの循環的複雑度を下げてください。
2
3
制約:
4
- 外部のインターフェース(引数・戻り値)を変更しないこと
5
- 既存のテストが全てPASSすること
6
- 新しいクラスは作成しないこと(メソッド分割のみ)
7
- 分割したメソッドにはプレフィックス _calc_ を付けること

なぜ制約が重要か

制約なしで「リファクタリングしてください」と言うと、AIは以下をやりがちです。

  1. 新しいクラスを作る — 必要のない抽象化が増える
  2. インターフェースを変更する — 呼び出し元の全修正が必要になる
  3. デザインパターンを導入する — Strategy pattern、Visitor patternなど、コードベースに馴染まない構造が入る

「外部インターフェースを変えない」「メソッド分割のみ」という制約で、安全にCCを下げられます。


分割のパターン

パターン1:条件分岐のメソッド抽出

1
# Before(CC寄与: +5)
2
def process(self, data):
3
if data.type == "A":
4
# 20行のロジック
5
elif data.type == "B":
6
# 20行のロジック
7
elif data.type == "C":
8
# 20行のロジック
9
10
# After(CC寄与: +3、各サブメソッドは+1)
11
def process(self, data):
12
if data.type == "A":
13
self._process_type_a(data)
14
elif data.type == "B":
15
self._process_type_b(data)
2 collapsed lines
16
elif data.type == "C":
17
self._process_type_c(data)

分岐の数は変わりませんが、各分岐の「中身」がサブメソッドに分離されることで、テスト可能性が大幅に向上します。

パターン2:ガード節の早期リターン

1
# Before(深いネスト、CC寄与: +4)
2
def execute(self, signal, config):
3
if signal is not None:
4
if signal.score > config.min_score:
5
if config.enabled:
6
if not self.is_circuit_breaker_active():
7
# 実際の処理
8
9
# After(ガード節で早期リターン、同じCC値だがネストが浅い)
10
def execute(self, signal, config):
11
if signal is None:
12
return
13
if signal.score <= config.min_score:
14
return
15
if not config.enabled:
4 collapsed lines
16
return
17
if self.is_circuit_breaker_active():
18
return
19
# 実際の処理(ネスト0段)

パターン3:計算ロジックのまとめ出し

1
# Before(__init__内にビジネスロジック)
2
def __init__(self, config):
3
self.config = config
4
# 50行の初期化ロジック
5
# パラメータ検証、デフォルト値設定、依存関係の解決...
6
7
# After
8
def __init__(self, config):
9
self.config = config
10
self._validate_config()
11
self._setup_defaults()
12
self._resolve_dependencies()

テスト戦略

リファクタリング時のテスト戦略は「回帰テストで既存の動作を保証する」が基本です。

回帰テスト

各エンジンのリファクタリング結果:

エンジン構造検証テスト回帰テスト結果
データ処理エンジンA20件4,119件全PASS
データ処理エンジンB35件2,064件全PASS
データ処理エンジンC33件1,270件全PASS

「構造検証テスト」は、分割後のメソッドが正しく呼び出されることを確認するテストです。「回帰テスト」は、リファクタリング前後で動作が変わらないことを確認します。

AIに回帰テストを先に実行させる

1
リファクタリングの手順:
2
1. まず現在の全テストを実行して、PASS状態を確認する
3
2. リファクタリングを実施する
4
3. 全テストを再実行して、PASS状態が維持されていることを確認する
5
4. 新しい構造検証テストを追加する

この順序をCLAUDE.mdに書いておくことで、AIが「リファクタリングしたがテストを壊した」パターンを防げます。


コード重複削減との連携

複雑度低減と並行して、3つのエンジン間のコード重複も削減しました。

1
BatchEngine(バッチ処理実行エンジン):
2
Before: 3モード × 1,247行 = 3,741行
3
After: 基底クラス1,787行 + 3モード差分 = 1,787行(52%削減)
4
手法: Template Method パターン(基底クラス + 9つのフックメソッド)

Template Method パターンは「全体の処理フローは基底クラスが定義し、モード固有の処理だけをサブクラスがオーバーライドする」設計です。

1
class BaseBatchEngine:
2
def run(self, pairs: list[str]) -> BatchResult:
3
data = self._load_data(pairs) # フック1
4
signals = self._generate_signals(data) # フック2
5
results = self._execute_tasks(signals) # フック3
6
return self._evaluate(results) # フック4
7
8
class StreamBatchEngine(BaseBatchEngine):
9
def _load_data(self, pairs):
10
# Stream固有のデータ読み込み(4時間間隔)
11
...
12
13
class IntervalBatchEngine(BaseBatchEngine):
14
def _load_data(self, pairs):
15
# Interval固有のデータ読み込み(1時間間隔)
1 collapsed line
16
...

学んだこと

1. AIはリファクタリングが得意

「メソッド分割」はAIにとって機械的な作業であり、ミスが少ないです。人間がやると「ついでにロジックも変えてしまう」リスクがありますが、AIは「制約通りに分割だけする」のが得意です。

2. CC 20以下を目標にする

CC 20以下なら、メソッドの動作を人間が頭の中でトレースできます。CC 50以上になると、変更を加えるたびに予測不能な副作用が生じます。

3. 回帰テストが安全網

7,000件以上の回帰テストが全PASSすることが、「リファクタリングで動作が変わっていない」ことの証拠になります。テストなしのリファクタリングは危険すぎます。

4. __init__()のCC50超は設計の問題

コンストラクタの複雑度が高いのは、初期化処理にビジネスロジックが混入しているサインです。初期化は「設定の保存」と「依存関係の解決」だけにして、ビジネスロジックは別メソッドに分離すべきです。


まとめ

AIリファクタリングで重要なのは以下の3点です。

  1. 制約の明示: 「外部インターフェース変更なし」「メソッド分割のみ」「新クラス作成禁止」を指定し、AIの過剰な設計変更を防止します
  2. 回帰テスト必須: 7,000件以上のテストで動作の不変性を保証します。テストなしのリファクタリングは禁止です
  3. CC 20以下を目標: CC 60→19(-68%)、CC 53→21(-60%)、CC 51→6(-88%)と、全3エンジンで達成しました

「複雑度が高いコードは怖くて触れない」——その恐怖をAI + 回帰テストが解消します。

Article title:AIにリファクタリングを任せる:循環的複雑度を60→19に下げた実践
Article author:45395
Release time:2026-04-03

記事へのご質問・ご感想をお聞かせください

フィードバックを送る