循環的複雑度はどのくらいに設定するべきか

ソースコードの複雑性を表す基準として、循環的複雑度という指標があります。
この循環的複雑度はどれくらいの設定値にするのが適切なのかをまとめておこうと思います。

循環的複雑度とは

循環的複雑度はThomas J. McCabe氏によって1976年に考案された指標です。
この値はソースコードの複雑性を示すものであり、この値を測定し、低い値を保つことで、ソースコードの保守性を高い状態で維持することができます。

循環的複雑度の目安

循環的複雑度の基準は、対象としているソフトウェアの複雑性や、ドメインの複雑性などに左右されます。
そのため、絶対の数値というものはありませんが、書籍などで紹介されているものの一部を見ていこうと思います。

MathWorks

MathWorksが公開している基準です。
循環的複雑度の指標としては一番引用される基準だと思います。

循環的複雑度 複雑さの状態 バグ混入確率
10以下 非常に良い構造 25%
30以上 構造的なリスクあり 40%
50以上 テスト不可能 70%
75以上 いかなる変更も誤修正を生む 98%

jp.mathworks.com

Software Architecture Metrics

ソースコードのアーキテクチャなどを評価する様々なメトリクスを紹介している書籍です。
日本語訳は現時点で出版されていません。

This metric is well researched, and we know that error rates increase quickly for all values above 24. I recommend a threshold of 15, to stay on the safe side.

 ↓訳

この指標はよく研究されており、24を超えるすべての値に対してエラー率が急速に増加することがわかっています。安全を確保するために、私は15の閾値を推奨します。

Software Architecture Metrics: Case Studies to Improve the Quality of Your Architecture

ソフトウェアアーキテクチャの基礎

色々なソフトウェアアーキテクチャなどを紹介している書籍です。
原文は英語ですが、日本語訳版も出版されています。

In general, the industry thresholds for CC suggest that a value under 10 is acceptable, barring other considerations such as complex domains. We consider that threshold very high and would prefer code to fall under five, indicating cohesive, well-factored code. 

↓訳(日本語訳版を持っていなかったため原文から引用します)

一般的に、業界でのCC(循環的複雑度)の閾値は、複雑な領域などの他の考慮事項を除けば、10未満であれば受け入れられるとされています。私たちはその閾値を非常に高く考えており、5未満であれば、凝縮性のある、適切にファクタリングされたコードを示していると考え、好ましいと考えています。

ソフトウェアアーキテクチャの基礎 ―エンジニアリングに基づく体系的アプローチ

実際のソースコード

では、実施にJavaのソースコードで、循環的複雑度が「5」のコード、「10」のコード、「15」のコードのサンプルを見てみたいと思います。
ソースコードの作成はChatGPTにお願いしていますが、CheckStyleでも確認しています。

循環的複雑度が5のコード

public class CyclomaticComplexityExample5 {
    public static void exampleMethod(int a, int b, int c, int d) {
        if (a > b) {
            // パス1
            System.out.println("a is greater than b");
        } else {
            // パス2
            System.out.println("b is greater than or equal to a");
        }
        for (int i = 0; i < c; i++) {
            // パス3
            System.out.println("i: " + i);
        }
        if (c > 10) {
            // パス4
            System.out.println("c is greater than 10");
        }
        if (d % 2 == 0) {
            // パス5
            System.out.println("d is even");
        }
    }
}

循環的複雑度が10のコード

public class CyclomaticComplexityExample10 {
    public static void exampleMethod(int a, int b, int c, int d, int e) {
        if (a > 0) { // 1
            System.out.println("a is positive");
        }
        if (b > 0) { // 2
            System.out.println("b is positive");
        }
        if (c > 0) { // 3
            System.out.println("c is positive");
        }
        if (d > 0) { // 4
            System.out.println("d is positive");
        }
        if (a < 0) { // 5
            System.out.println("a is negative");
        }
        if (b < 0) { // 6
            System.out.println("b is negative");
        }
        if (c < 0) { // 7
            System.out.println("c is negative");
        }
        if (d < 0) { // 8
            System.out.println("d is negative");
        }
        if (e != 0) { // 9
            System.out.println("e is non-zero");
        }
        // デフォルトパス(10)
        System.out.println("Check complete");
    }
}

循環的複雑度が15のコード

public class CyclomaticComplexityExample15 {
    public static void exampleMethod(int a, int b, int c, int d, int e) {
        if (a > 0) { // 1
            System.out.println("a is positive");
        }
        if (b > 0) { // 2
            System.out.println("b is positive");
        }
        if (c > 0) { // 3
            System.out.println("c is positive");
        }
        if (d > 0) { // 4
            System.out.println("d is positive");
        }
        if (e > 0) { // 5
            System.out.println("e is positive");
        }
        if (a < 0) { // 6
            System.out.println("a is negative");
        }
        if (b < 0) { // 7
            System.out.println("b is negative");
        }
        if (c < 0) { // 8
            System.out.println("c is negative");
        }
        if (d < 0) { // 9
            System.out.println("d is negative");
        }
        if (e < 0) { // 10
            System.out.println("e is negative");
        }
        if (a == 0) { // 11
            System.out.println("a is zero");
        }
        if (b == 0) { // 12
            System.out.println("b is zero");
        }
        if (c == 0) { // 13
            System.out.println("c is zero");
        }
        if (d == 0) { // 14
            System.out.println("d is zero");
        }
        // デフォルトパス(15)
        System.out.println("Evaluation complete");
    }
}

このサンプルコードは処理がすごく簡単なものですが、循環的複雑度が「15」のコードは条件分岐の量がそれなりに多く、しっかり処理がある場合にはそれなりの複雑なコードになりそうです。

まとめ

循環的複雑度の目安について見てみました。

基本的には「10」、もしくは「15」以下に設定するのが良さそうです。
それよりも更に厳しめに設定したい場合は、「5」や、5と10の中間の「8」などに設定するのが良いのではないでしょうか?
ただし、これらの設定値はソフトウェアの複雑性などのより一概には決められませんので、新規開発の際は最初は厳し目の値にしておいて、その基準を守るのがコストや保守性に見合わないと判断した場合に基準値を緩くしていくのが良いと思います。