循環的複雑度が高いコードはリファクタリングの優先候補だ
要約
循環的複雑度(Cyclomatic Complexity)でコードの分岐数を客観的に数値化すれば、次にリファクタリングすべき関数の優先度をデータで決められる。感覚論ではなく計測で判断するためのツール選定から実装まで解説する。
循環的複雑度が高いコードはリファクタリングの優先候補だ
循環的複雑度(Cyclomatic Complexity)は、次に何をリファクタリングすべきかを教えてくれる数値だ。感覚ではなく、計測で判断できる。
循環的複雑度とは何か、なぜdevが今すぐ計測すべきか
循環的複雑度は、1976年にThomas McCabeが定義したメトリクスだ。ひとつの関数内に存在する線形独立なパスの数を測る。if、for、while、caseなどの分岐が増えるほどスコアが上がる。
スコアが1なら分岐なし。スコアが15なら、その関数をテストするには最低15個のテストケースが必要という意味だ。計算式はシンプルだ。CC = 判断ノード数 + 1。
この数値を知っておく実用的な理由が3つある。まず、テストに必要なケース数が事前にわかる。次に、バグが潜みやすい関数を客観的に特定できる。そして、コードレビューの議論を感覚論から数値論に変えられる。
CC 1〜10は緑、11以上はアラート、25以上は負債確定
業界のコンセンサスは明快だ。
1〜10:シンプル。テストしやすい。リファクタリング不要。
11〜25:複雑。リファクタリング候補。テストコストが跳ね上がる。
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個のテストが必要だ。それが揃っていない状態でコードを触るのは、セーフティネットなしの綱渡りだ。
テストを先に書くことで、もうひとつの利点が生まれる。テストを書く段階で「この関数は何をするのか」が明確になり、分割のヒントが出てくることが多い。

循環的複雑度を下げる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するかは、計測結果が答えを持っている。