IT用語集

サイクロマティック複雑度とは? 10分でわかりやすく解説

水色の背景に六角形が2つあるイラスト 水色の背景に六角形が2つあるイラスト
アイキャッチ
目次

サイクロマティック複雑度は、ソースコードの「分岐の多さ」を手がかりに制御フローの複雑さを数値で表す指標です。値が大きいほど理解・変更・テストが難しくなりやすいため、保守性や品質面のリスクを早めに見つけるための目安になります。本記事では、定義と計算の考え方、目安の捉え方、現場で複雑度を下げる具体策までをまとめます。

サイクロマティック複雑度とは何か

サイクロマティック複雑度とは、プログラムの構造的な複雑さを数値化したものです。ソースコードに含まれる分岐が増えるほど値が大きくなり、保守性やテストのしやすさを見積もる目安として利用されます。

サイクロマティック複雑度の定義

サイクロマティック複雑度は、制御フローグラフ(CFG)における「線形独立なパスの数」として定義されます。CFGは、プログラムの実行経路を表すグラフで、ノードが処理の単位、エッジが制御の流れを示します。このとき、独立した経路が増えるほど、読解・変更・テストに必要な手間も増えやすくなります。

サイクロマティック複雑度を測る理由

サイクロマティック複雑度を測定する主な理由は次のとおりです。

  1. 保守性の目安にする
    複雑度が高いほど、コードの理解や修正が難しくなりがちです。数値として把握すると、手を入れる優先順位を決めやすくなります。
  2. テスト設計のヒントにする
    複雑度は「分岐がどれだけあるか」を反映するため、最低限のテスト観点(経路)の数を考える手がかりになります。ただし、実際の必要テスト数は仕様や境界値、例外系などでも増えるため、複雑度はあくまで出発点として扱うのが安全です。
  3. リファクタリングの判断材料にする
    複雑度が突出している箇所は、バグが入り込みやすく、レビューでも見落としが起きやすい部分です。数値で注意すべき箇所を特定できると、改善計画を立てやすくなります。

サイクロマティック複雑度の計算方法

サイクロマティック複雑度は、次の式で計算できます。

サイクロマティック複雑度 = E - N + 2P

  • E:CFGにおけるエッジの数
  • N:CFGにおけるノードの数
  • P:CFGにおける連結成分の数(多くの関数では1)

また、実務では分岐の数に着目して、おおよそ次のようにカウントする方法も使われます。

  1. if文、for文、while文などの分岐:+1
  2. 論理演算子(&&、||):+1(言語や計測ツールの定義に従う)
  3. switch文のcase句:+1(defaultの扱いはツール定義に従う)

1つ目の式はCFGを前提にした厳密な定義で、2つ目は現場で扱いやすい近似です。どちらを使うにせよ、「同じ基準で継続的に測り、増減を見る」ことが重要です。

なお「10以上は悪い」といった閾値は、よく参照される目安ではありますが絶対ではありません。言語、チームのレビュー文化、テストの厚み、関数の責務などで適切な水準は変わります。数字だけで判断せず、変更頻度や障害履歴と合わせて捉えましょう。

サイクロマティック複雑度は、コードの状態を定量的に眺めるための指標です。日々のレビューやリファクタリングの判断に取り入れることで、保守性を高めやすくなります。

サイクロマティック複雑度の重要性

サイクロマティック複雑度は、コードの扱いやすさを早めに把握するための指標です。適切に管理できると、読みやすさの改善、バグの抑制、テスト設計の効率化につながります。

コードの可読性とサイクロマティック複雑度の関係

サイクロマティック複雑度が高いコードは、分岐が多く、追うべき経路が増えるため、読み手の負荷が上がりやすくなります。可読性を上げたいなら、分岐を減らすというより「分岐を局所化して見通しを良くする」意識が効きます。

サイクロマティック複雑度が高いコードのリスク

サイクロマティック複雑度が高いコードでは、次のようなリスクが増えやすくなります。

  1. 意図しない経路が残り、バグが入り込みやすい
  2. 変更時に影響範囲の読み違いが起きやすい
  3. テスト観点が膨らみ、漏れが起きやすい

重要なのは「複雑度が高い=即ダメ」ではなく、「高い部分は壊れやすい前提で扱う」ことです。レビューを厚くする、テストを増やす、責務を切る、といった対策につなげましょう。

サイクロマティック複雑度を下げるためのリファクタリング

複雑度が高いコードを改善するには、リファクタリングが有効です。代表的な方向性は次のとおりです。

  1. 関数・メソッドを分割して責務を絞る
  2. 条件分岐を単純化し、読み順を整える
  3. ネストを浅くして見通しを良くする
  4. 重複コードをまとめて分岐の発生源を減らす

リファクタリングは、動作が変わっていないことの確認が肝です。テストを用意し、段階的に進めるのが安全です。

サイクロマティック複雑度を意識したコーディングスタイル

複雑度が増え続けないようにするには、日頃の書き方が効きます。次のようなスタイルが実務では役に立ちます。

スタイル説明
関数・メソッドを小さくする1つの関数は1つの役割に寄せ、読む範囲を狭くする
条件分岐を簡潔にする複雑な条件式は名前付きの判定に切り出し、意図を明示する
ネストを浅くする早期リターンやガード句で、読み順をまっすぐにする
早期リターンを活用する例外・エラー・前提条件の否定を先に返し、本筋を見やすくする

サイクロマティック複雑度は、品質改善のきっかけを作る指標です。数字を追うだけでなく、読みやすさと変更しやすさに結びつけて使いましょう。

サイクロマティック複雑度の適切な値

サイクロマティック複雑度は便利ですが、適切な値はプロジェクトの状況で変わります。ここでは、よく使われる目安と、現場での扱い方を整理します。

一般的なサイクロマティック複雑度の基準

一般に、次のような目安が紹介されることがあります。

複雑度評価の目安
1-10比較的シンプルで、把握しやすい
11-20やや複雑で、レビューやテストを厚くしたい
21-50複雑で、分割や設計見直しを検討したい
50以上非常に複雑で、局所改善だけでは追いつかない可能性がある

よく「10以下が理想」と言われますが、これは万能な正解ではありません。責務が明確でテストが厚い関数なら、多少高くても運用できる場合があります。一方で変更頻度が高いのに複雑なまま放置されている箇所は、早めに手当てしたほうが事故を減らせます。

プロジェクトの規模とサイクロマティック複雑度

規模が大きいほど複雑な条件を抱えがちですが、それを理由に放置すると将来の変更コストが跳ね上がります。現実的には、次のように考えると運用しやすくなります。

  • 小規模:低い閾値で早めに直し、コードベースを小さく保つ
  • 中規模:影響範囲の大きい箇所から優先して整える
  • 大規模:闇雲に閾値を緩めず、変更頻度・重要度・障害履歴で優先順位を付ける

許容値を上げるより、注意すべき箇所を特定して守りを厚くするほうが、実務では効果が出やすいです。

関数やモジュールごとのサイクロマティック複雑度の管理

複雑度は、関数・メソッド単位で見るのが分かりやすい出発点です。1つの関数が複数の責務を抱えると、分岐が増えて複雑度も上がりやすくなります。粒度を整えるだけでも、複雑度は下がることが多いです。

クラスやモジュール全体の複雑度も参考になりますが、まずは日々触る関数や、障害が出た関数から手当てするほうが成果につながります。

サイクロマティック複雑度の閾値設定とアラート

プロジェクトで閾値を決め、超えたら見える化するのは有効です。ただし、アラートを増やしすぎると形骸化します。運用としては、次のような設計が現実的です。

  1. 一定値を超えたら「要レビュー」扱いにする
  2. さらに超えたら「分割を検討する」タスクを切る
  3. 例外として許容する場合は、理由と代替策(テストを厚くする、変更を抑えるなど)を明文化する

閾値は固定ではなく、チームの成熟度や品質目標に合わせて見直す前提で置くと、運用が破綻しにくくなります。

サイクロマティック複雑度を改善する方法

複雑度が高いコードは、読みづらく、変更で壊れやすくなります。ここでは、複雑度を現実的に下げるための手段を具体例とともに整理します。

関数の分割と単一責任の原則

複雑度を下げるうえで効きやすいのは、関数を分割して責務を絞ることです。1つの関数は1つの責任に寄せると、分岐が局所化され、読み順が整います。

例えば、次のようなネストの深い関数があるとします。


public void processOrder(Order order) {
if (order.isValid()) {
if (order.getTotal() > 1000) {
if (order.hasDiscount()) {
// 割引を適用する処理
} else {
// 通常の処理
}
} else {
// 合計金額が1000以下の場合の処理
}
} else {
// 無効な注文の処理
}
}

これを、読み順がまっすぐになるように分割すると、意図が追いやすくなります。


public void processOrder(Order order) {
if (!order.isValid()) {
handleInvalidOrder(order);
return;
}
if (order.getTotal() <= 1000) {
processSmallOrder(order);
return;
}
if (order.hasDiscount()) {
processDiscountedOrder(order);
} else {
processNormalOrder(order);
}
}

private void handleInvalidOrder(Order order) {
// 無効な注文の処理
}
private void processSmallOrder(Order order) {
// 合計金額が1000以下の場合の処理
}
private void processDiscountedOrder(Order order) {
// 割引を適用する処理
}
private void processNormalOrder(Order order) {
// 通常の処理
}

分割は、やりすぎると追跡が難しくなる場合があります。切り出した関数名が説明になっているか、引数が増えすぎていないか、まで含めて判断しましょう。

条件分岐の簡素化とガード句の活用

複雑な条件分岐は、複雑度だけでなく読みづらさの原因にもなります。ガード句(前提を満たさない場合に早期に抜ける書き方)を使うと、ネストを減らしやすくなります。

ガード句は、例外・エラー・前提条件の否定を先に処理して、本筋を見やすくするための書き方です。

例:


public void processOrder(Order order) {
if (!order.isValid()) {
handleInvalidOrder(order);
return;
}
// 注文処理
}

また、条件式そのものが長くなる場合は、判定を意味のある名前のメソッドに切り出すと、読み手が「何を判定しているか」を把握しやすくなります。

ループの最適化と読みやすい反復

ループ処理も複雑度に影響します。ポイントはループそのものをなくすことではなく、ループ内の分岐を増やしすぎないことです。

反復をシンプルにする方法として、拡張for文やイテレータ(言語機能としての反復)を使うと、インデックス管理のノイズを減らせます。

例:


List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
for (String name : names) {
System.out.println(name);
}

ストリームAPIなどの表現も有効ですが、可読性はチームの慣れに左右されます。短い=読みやすいとは限らないため、レビューで合意できる形に揃えるのが安全です。

設計パターンの適用による分岐の局所化

分岐が増える根本原因が種類の追加や条件の増殖である場合、設計で分岐の位置を変えると効果があります。例えばStrategyパターンのように振る舞いを差し替えられる構造にすると、巨大なif/switchを小さなクラスやメソッドへ分散できます。

ただし、パターン適用は万能薬ではありません。抽象化が過剰になると逆に追いづらくなるため、増え続ける分岐を抱えている箇所に絞って適用するのが現実的です。

サイクロマティック複雑度は、改善の方向性を決めるための道具です。関数分割、ガード句、責務の切り分けを組み合わせ、読みやすく壊れにくい構造へ整えていきましょう。

まとめ

サイクロマティック複雑度は、分岐の多さを手がかりに、コードの扱いやすさを定量的に眺める指標です。数値が高い部分は理解・変更・テストが難しくなりやすいため、レビューや改善の優先順位付けに役立ちます。プロジェクトに合った閾値を決めて継続的に測定し、複雑度が上がりやすい箇所は関数分割やガード句、責務の切り分けで見通しを良くしていきましょう。

よくある質問

サイクロマティック複雑度とは何ですか?

サイクロマティック複雑度は、分岐の多さを手がかりに、制御フローの複雑さを数値で表す指標です。値が大きいほど、追うべき実行経路が増え、理解やテストが難しくなりやすい傾向があります。

なぜサイクロマティック複雑度が重要なのですか?

複雑度が高い部分は、読み違いや変更時の影響範囲の見落としが起きやすく、バグや修正コストが増えやすいからです。数値で把握できると、改善の優先順位やレビューの重点を決めやすくなります。

サイクロマティック複雑度は何を基準に増えますか?

主に条件分岐やループ、switchのcaseなど、実行経路を増やす構造で増えます。論理演算子の扱いなどはツール定義で差があるため、同じ基準で継続的に測ることが重要です。

サイクロマティック複雑度が高いと何が困りますか?

コードの意図を追いにくくなり、変更の影響範囲を読み違えやすくなります。また、テスト観点が増えるため、テスト漏れが起きやすく、品質を維持しづらくなります。

サイクロマティック複雑度を測るメリットは何ですか?

どこが壊れやすいか、どこにレビューや改善を集中すべきかを定量的に把握できます。複雑度の推移を追うことで、改善の効果や、仕様追加による劣化も見えやすくなります。

サイクロマティック複雑度の注意点はありますか?

閾値は目安であり、数字だけで良し悪しを断定すると判断を誤りやすい点です。責務が明確でテストが厚い場合は運用できることもあるため、変更頻度や障害履歴と合わせて判断するのが安全です。

サイクロマティック複雑度はどんな場面で役立ちますか?

コードレビューの重点決め、リファクタリング対象の選定、品質メトリクスの定点観測などで役立ちます。特に、複雑度が突出している関数を先に手当てすると効果が出やすいです。

サイクロマティック複雑度とテストケース数は関係しますか?

複雑度は実行経路の数を反映するため、最低限のテスト観点を考えるヒントになります。ただし実際のテスト設計は、仕様の境界値や例外系、データパターンでも増えるため、複雑度は目安として扱うのが適切です。

サイクロマティック複雑度を下げるにはどうすればよいですか?

関数の責務を絞って分割し、ガード句でネストを浅くし、複雑な条件式は判定を切り出して意図を明確にします。分岐が増え続ける構造なら、設計を見直して分岐を局所化するのも有効です。

サイクロマティック複雑度の結論として何を押さえるべきですか?

複雑度は、コードの扱いやすさを測るための指標であり、改善の優先順位付けに役立ちます。数字だけに引きずられず、同じ基準で継続的に測り、必要な箇所にレビュー・テスト・リファクタリングを集中することが重要です。

記事を書いた人

ソリトンシステムズ・マーケティングチーム