py-complexity について
py-complexityは、Python開発者向けにコードの複雑さを測定し、低減するスキルです。リファクタリングと推移の追跡を案内し、CI/CDで基準値を適用できます。
主な機能
- 01循環的複雑度と認知的複雑度の測定
- 02複雑な関数と長いファイルの検出
- 03リファクタリングパターンの適用
- 04Git履歴に沿った複雑度の追跡
- 05CI/CDでのしきい値適用
活用シーン
- 01リファクタリングが必要なPythonコードの特定
- 02ネストと重複ロジックの削減
- 03開発中の複雑度変化の監視
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ l-mb/ python-refactoring-skills --skill py-complexityインストール後、Claude Code で/py-complexityを入力して読み込まれたか確認します。
詳細
Python の複雑さの軽減
コードの複雑さを軽減し、保守性と理解しやすさを向上させます。
コンテキストウィンドウを効果的に活用します。
目標
- 循環的複雑度と認知的複雑度を測定する
- 複雑すぎる関数やモジュールを特定する
- 長すぎるファイルを特定する(避けられない場合を除き、コードファイルは500行以下にする)
- リファクタリングパターンを適用して複雑さを軽減する
- 複雑さの改善を継続的に追跡する
- CI/CD で複雑度のしきい値を適用する
必須ツール
[dependency-groups] の dev に追加:"radon"、"lizard"、"xenon"、"wily"
- radon:循環的複雑度と保守性インデックス
- lizard:認知的複雑度(可読性の評価に適しています)
- xenon:CI/CD でしきい値を適用
- wily:git の履歴に沿って傾向を追跡
権限:まず py-quality-setup を実行して、必要なツールの権限をすべて .claude/settings.local.json に設定します。
検出フェーズ
複雑度を測定する
# Key commands
radon cc . -n C # Functions with complexity ≥11
lizard -C 15 . # Cognitive complexity warnings
radon mi . -n B # Maintainability index <65
wily build . # Initialize tracking (one-time)
wily diff HEAD~10 # Compare trends
scc --by-file --ci # Optional: code statistics (called 'sccount' on openSUSE)
しきい値
- 循環的複雑度:C(11以上)以上でリファクタリング
- 認知的複雑度:15を超えたらリファクタリング
- 保守性:65未満でリファクタリング
- コードファイルの行数:大幅に500行を超える場合は、避けられない場合を除き分割
手動でパターンを検出する
Explore エージェントを使って、複雑さを増す以下のパターンを探します。
マジックナンバーと文字列:
- コード内に散在する数値リテラル(0、1、-1 を除く)
- キー、しきい値、設定値として使われる文字列リテラル
- 検索:複数桁の数値には
grep -rE '\b[0-9]{2,}\b' --include='*.py'を使用 - 確認する箇所:料金計算、タイムアウト値、バッファサイズ、正規表現パターン
繰り返されるフィールド操作:
- オブジェクトのフィールドを確認・設定する、類似した複数の if 文
- パターン:
if obj.field: obj.field = func(obj.field) - 検索:異なるフィールドに対して類似した処理を5行以上行う関数
- 候補:設定の読み込み、検証、シリアル化、フィールドのクリア
繰り返される複雑な型定義:
- 複数の場所で使われる同じ複雑な型注釈
- パターンの例:関数やクラスで繰り返される
Literal["a", "b", "c"] | None - 検索:
grep -r 'Literal\[' --include='*.py'を実行し、重複を確認 - その他:複雑な共用体型、複数回使われる入れ子のジェネリック型
- 候補:関数パラメーター、戻り値の型、クラス属性、cast() 呼び出し
長い if/elif チェーン:
- 類似した処理を行う条件分岐が5つ以上ある
- ルックアップテーブルやポリモーフィズムで置き換えられることがよくあります
深くネストされたコード:
- インデントが4段階以上
- 複数のループや条件分岐が入れ子になっている
リファクタリングパターン
関数の抽出
複雑な関数を、目的に特化したサブ関数に分割します。
# BEFORE - 20+ lines, complexity: 15
def process_order(order: dict) -> bool:
# Validation, payment, confirmation logic all mixed
# AFTER - Each function <5 lines, complexity: <5
def process_order(order: dict) -> bool:
return is_valid_order(order) and process_payment(order) and complete_order(order)
ガード節
ネストされた条件を早期リターンに置き換えます。
# BEFORE - Deep nesting
if user:
if user.get("active"):
if user.get("verified"):
return True
return False
# AFTER - Guard clauses
if not user or not user.get("active") or not user.get("verified"):
return False
return True
ルックアップテーブル
if/elif チェーンを辞書に置き換えます。
# BEFORE - Nested conditionals (complexity: 7)
if customer == "gold" and total > 1000: return 0.20
elif customer == "gold": return 0.15
# ... more branches
# AFTER - Lookup table (complexity: 2)
RATES = {("gold", "high"): 0.20, ("gold", "low"): 0.15, ...}
return RATES.get((customer, tier), 0.0)
マジックナンバーと文字列の抽出
散在するリテラルを設定クラスにまとめます。
# BEFORE - Magic numbers throughout code
if amount < 100: base_fee = 2.50
elif amount < 1000: base_fee = 5.00
if account_type == "premium": return base_fee * 0.5
# AFTER - Configuration class
class FeeConfig:
TIER_SMALL = 100
FEE_SMALL = 2.50
PREMIUM_DISCOUNT = 0.5
if amount < FeeConfig.TIER_SMALL:
base_fee = FeeConfig.FEE_SMALL
メリット:信頼できる唯一の情報源となり、変更が容易で、コードの自己説明性が高まり、環境から読み込めます。
繰り返されるフィールド操作の置き換え
繰り返される if 文を、フィールド名を使ったループに置き換えます。
# BEFORE - 15 similar if-statements (complexity: 9)
if config.email.smtp_host:
config.email.smtp_host = substitute_secret(config.email.smtp_host, secrets)
if config.email.smtp_user:
config.email.smtp_user = substitute_secret(config.email.smtp_user, secrets)
# ... 13 more similar lines
# AFTER - Loop over field list (complexity: 3)
email_fields = ["smtp_host", "smtp_user", "smtp_password", "smtp_from"]
for field in email_fields:
if value := getattr(config.email, field, None):
setattr(config.email, field, substitute_secret(value, secrets))
パターン:検証、フィールドのクリア、変換、シリアル化には、getattr/setattr のループを使います。
繰り返される複雑な型定義の抽出
繰り返される複雑な型注釈を TypeAlias に置き換えます。
# BEFORE - Type repeated 8 times across files
cache_mode: Literal["use", "only", "refresh"] | None
# ... used in 8 different functions, classes
# AFTER - Define once, use everywhere
CacheMode = Literal["use", "only", "refresh"]
CacheModeOptional = CacheMode | None
cache_mode: CacheModeOptional
配置場所:
- モジュールレベル:1つのモジュール内で使われる型
types.py:プロジェクト全体で使われる型__init__.py:パッケージ全体で公開する型
メリット:DRY、意味が明確な名前、変更の容易さ、タイプミスの削減
検証チェックリスト
-
radon cc . -n Cで、複雑度がC(11以上)以上の関数が報告されない -
lizard -C 15 .で、認知的複雑度に関する警告が報告されない -
radon mi . -n Bで、保守性インデックスが65未満のモジュールが報告されない - コードファイルが500行を超えていない(避けられない場合を除く)
- 追跡用に
wily build .が初期化されている - リファクタリング後にすべてのテストが合格する
- コードカバレッジが維持または向上している
例
例:複雑さを軽減するワークフロー
1. Measure: radon cc . -n C; lizard -CCN 15 .
2. Found: handlers.py:process_data (complexity D: 25)
3. Apply patterns: Extract functions, guard clauses, lookup tables
4. Result: 4 functions with complexity A-B
5. Track: wily diff HEAD~1 shows 20-point reduction
例:専門的なパターンを適用する
# Magic numbers: Search with grep, extract to config class
if amount < 100: fee = 2.50 # BEFORE
if amount < FeeConfig.TIER_SMALL: fee = FeeConfig.FEE_SMALL # AFTER
# Repetitive fields: Replace 15 if-statements with loop
for field in ["smtp_host", "smtp_user", ...]:
if value := getattr(config, field, None):
setattr(config, field, process(value))
# Complex types: Extract repeated Literal types to TypeAlias
CacheMode = Literal["use", "only", "refresh"] # Used 8 times → defined once
関連スキル
- 前提条件:py-quality-setup(ツール設定)、py-test-quality(リファクタリング前の安全網)
- 事前のクリーンアップ:py-code-health(まず不要なコードを削除してノイズを減らします)
- 適用:py-git-hooks(複雑さのチェックを pre-commit に追加します)