循環的複雑度が高いコードはリファクタリングの優先候補だ

要約

循環的複雑度(Cyclomatic Complexity)でコードの分岐数を客観的に数値化すれば、次にリファクタリングすべき関数の優先度をデータで決められる。感覚論ではなく計測で判断するためのツール選定から実装まで解説する。

循環的複雑度:コードの複雑さを数値で示すイラスト

循環的複雑度が高いコードはリファクタリングの優先候補だ

循環的複雑度(Cyclomatic Complexity)は、次に何をリファクタリングすべきかを教えてくれる数値だ。感覚ではなく、計測で判断できる。

循環的複雑度とは何か、なぜdevが今すぐ計測すべきか

循環的複雑度は、1976年にThomas McCabeが定義したメトリクスだ。ひとつの関数内に存在する線形独立なパスの数を測る。ifforwhilecaseなどの分岐が増えるほどスコアが上がる。

スコアが1なら分岐なし。スコアが15なら、その関数をテストするには最低15個のテストケースが必要という意味だ。計算式はシンプルだ。CC = 判断ノード数 + 1

この数値を知っておく実用的な理由が3つある。まず、テストに必要なケース数が事前にわかる。次に、バグが潜みやすい関数を客観的に特定できる。そして、コードレビューの議論を感覚論から数値論に変えられる。

CC 1〜10は緑、11以上はアラート、25以上は負債確定

業界のコンセンサスは明快だ。

McCabe自身は10を上限として推奨した。GoogleのEngineering Practices、MicrosoftのCode Analysis、NASAのコーディング標準もこの数字を参照している。

CC25の関数を考えてみる。分岐が25本あるということは、完全なテストカバレッジには最低25個のテストケースが必要だ。そのうちの1本でも漏れれば、バグは本番環境で出る。

どこから手をつける?変更頻度との掛け合わせが答えだ

高複雑度 × 高変更頻度の関数が最優先だ。変更されないコードが複雑でも、バグのリスクは低い。書き直すコストに見合わない。

次のgitコマンドで変更頻度の高いファイルを特定できる:

git log --format=format: --name-only | grep -v "^$" | sort | uniq -c | sort -rn | head -20

この出力とCCスコアを組み合わせると、優先度マトリクスが作れる。CCが20でコミット頻度が月1回のファイルより、CCが12で週3回触るファイルのほうが先に直すべきだ。

CodeSceneはこの「ホットスポット分析」を自動化している。循環的複雑度 × コミット頻度でヒートマップを生成し、次に何を直すかを一目で示す。

循環的複雑度と変更頻度で優先度を可視化するヒートマップ

SonarQube、CodeScene、Code Climate:3ツールの実力差

ツールによって計算の粒度と提供する洞察が違う。知っておくべき差異がある。

SonarQubeは関数ごと、クラスごと、プロジェクト全体のCCを出す。オープンソースのCommunity Editionで十分計測できる。CIに組み込むならSonarQube Scannerを使う。既存のGitHub Actions / GitLab CI連携も揃っている。

CodeSceneはCCをコミット履歴と組み合わせる。「技術負債の利子」を可視化するのが強みだ。前述のホットスポット分析が自動化されており、SaaS版の料金は2026年時点で月$39から始まる。

Code Climate QualityはGitHub PRに自動でCC差分を表示する。CIパイプライン前提で設計されており、設定が最も簡単だ。PRを出すたびにCCの増減が数値で見える。

リファクタリング前にテストを書く:順序を間違えるとリグレッションになる

複雑な関数にテストがなければ、リファクタリングはリグレッションを招く。順序がある。先にテストを書き、次にリファクタリングする。

ルールは単純だ。循環的複雑度 = 最低限必要なテストケース数。CCが12の関数には、少なくとも12個のテストが必要だ。それが揃っていない状態でコードを触るのは、セーフティネットなしの綱渡りだ。

テストを先に書くことで、もうひとつの利点が生まれる。テストを書く段階で「この関数は何をするのか」が明確になり、分割のヒントが出てくることが多い。

テストカバレッジとリファクタリングの関係:CCが指示する最小テスト数

循環的複雑度を下げる4つの実践的な手法

1. Guard Clause(早期リターン)で条件ネストを潰す

ネストされたifの代わりに、条件が満たされない場合に即座にreturnする。

# Before(CC: 5)
def process(data):
    if data is not None:
        if data.is_valid():
            if data.size > 0:
                return data.transform()
    return None

# After(CC: 2)
def process(data):
    if data is None or not data.is_valid() or data.size == 0:
        return None
    return data.transform()

2. ポリモーフィズムでswitch/caseを置き換える

switch文は各caseがパスを追加する。Strategy PatternやFactoryでCCを分散させる。型ごとのロジックをクラスに移すだけでスコアが3〜5下がることが多い。

3. 関数を分割して単一責任に戻す

1000行の関数は存在してはいけない。「この関数は何をするか」を一文で言えない場合、分割のサインだ。目安として、関数が20行を超えたら分割の必要性を検討する。

4. Fail Fastで入力バリデーションを先頭に集める

入力バリデーションを関数の先頭に集約し、主要ロジックのネスト深さを下げる。エラー処理が先頭に来ると、残りのコードはハッピーパスだけになる。可読性も上がる。

CI/CDに組み込んで計測ループを自動化する

SonarQubeをGitHub ActionsやGitLab CIに入れれば、PRごとにCC差分が出る。

# .github/workflows/sonar.yml
- name: SonarQube Scan
  uses: SonarSource/sonarqube-scan-action@master
  env:
    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
    SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

Quality Gateを設定する。CCが10を超える新関数はマージをブロックする。レガシーコードには猶予を与え、新コードには厳しくする。この2層運用が現実的だ。

計測が自動化されると、「複雑に見える」という感覚論がコードレビューから消える。「このコードのCCは22で、変更頻度ランク3位だ」という言い方ができる。

次に何をbuildするかの判断材料として使う

新機能を追加するか、既存コードをリファクタリングするか。この判断はデータで下せる。

コードベースの複雑度スコアの中央値が15を超えているなら、新機能追加は技術負債の上に技術負債を積む行為だ。スコアを8〜10以下に戻してからbuildする。

逆に中央値が5〜7なら、コードベースは健全だ。自信を持って次のフィーチャーに進める。

ツールが出す数字は感情論を消す。「このコードは複雑に見える」ではなく「このコードのCCは22で、変更頻度ランク3位だ」。そう言えるのが強みだ。次に何をbuildするかは、計測結果が答えを持っている。

よくある質問

循環的複雑度の許容範囲はどのくらいですか?
McCabeのオリジナル推奨は1〜10です。NASA、Microsoftも同じ数字を基準にしています。実用上は関数の目的次第で15まで許容するチームもありますが、20を超えたら問題と考えてください。
どのツールで循環的複雑度を計測できますか?
SonarQube(OSS版あり)、CodeScene、Code Climate Qualityが主要3択です。小規模プロジェクトならlizard(Python製CLIツール)で十分です:`pip install lizard && lizard ./src`で即座に計測できます。
複雑度が高いコードは必ずリファクタリングすべきですか?
変更頻度が低く、十分なテストがあるなら後回しで構いません。優先度は「複雑度 × 変更頻度」で決めてください。触らないコードを無理に直すリスクを避けることも大切です。
テストカバレッジと循環的複雑度の関係は?
CCはテストに必要な最低パス数を示します。CC12の関数に5テストしかなければ、カバレッジ不足が確定しています。テストを書く前に循環的複雑度を確認すると、必要なテスト数の目標が立てやすくなります。
レガシーコードへの適用方法を教えてください。
まず計測だけ実行し、現状のベースラインを記録してください。次のスプリントから「このファイルに触ったらCCを下げる」ルールを適用するボーイスカウト方式が現実的です。一度に全部直そうとしないことが重要です。
チームで循環的複雑度をどう共有しますか?
SonarQubeのダッシュボードをチームの週次レビューに組み込む方法が効果的です。Code Climate QualityやCodeClimateをPRに連携させ、コードレビュー時にCC差分が自動で表示される設定も有効です。
数値を下げるだけで品質は上がりますか?
CCは必要条件であって十分条件ではありません。スコアを下げるためだけに「変数を増やして分岐を隠す」リファクタリングは逆効果です。可読性と合わせて判断してください。目標はCCが低くて読みやすいコードです。