mcpField
P
py-complexity

py-complexity

github.com/l-mb開発5667 回インストール34 週間前に更新
リポジトリ

py-complexity について

py-complexityは、Python開発者向けにコードの複雑さを測定し、低減するスキルです。リファクタリングと推移の追跡を案内し、CI/CDで基準値を適用できます。

主な機能

  1. 01循環的複雑度と認知的複雑度の測定
  2. 02複雑な関数と長いファイルの検出
  3. 03リファクタリングパターンの適用
  4. 04Git履歴に沿った複雑度の追跡
  5. 05CI/CDでのしきい値適用

活用シーン

  1. 01リファクタリングが必要なPythonコードの特定
  2. 02ネストと重複ロジックの削減
  3. 03開発中の複雑度変化の監視

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/l-mb/python-refactoring-skills --skill py-complexity

インストール後、Claude Code で/py-complexityを入力して読み込まれたか確認します。

詳細

Python の複雑さの軽減

コードの複雑さを軽減し、保守性と理解しやすさを向上させます。

コンテキストウィンドウを効果的に活用します。

目標

  1. 循環的複雑度と認知的複雑度を測定する
  2. 複雑すぎる関数やモジュールを特定する
  3. 長すぎるファイルを特定する(避けられない場合を除き、コードファイルは500行以下にする)
  4. リファクタリングパターンを適用して複雑さを軽減する
  5. 複雑さの改善を継続的に追跡する
  6. 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 に追加します)

コメント