プログラミング
C# における巡回的複雑度(Cyclomatic Complexity)
Cyclomatic Complexity in C# (blog.ndepend.com)
要約
C# の巡回的複雑度(CC)は、メソッド内の線形に独立した実行パスの数を数えるコードメトリックです。これは、メソッド内の条件分岐構造の数に 1 を加えたもので、スコアが高いほどコードの可読性、テスト容易性、変更の安全性が低下します。この記事では、CC の計算方法、実用的な閾値、測定・可視化方法、そしてテストカバレッジとの組み合わせによる活用法を C# の例と共に解説します。
全文翻訳
2026年5月25日 9分で読めます
C# の巡回的複雑度(Cyclomatic Complexity、CC)は、メソッド内の線形に独立した実行パスの数を数えるコードメトリックです。具体的には、メソッド本体内の分岐構造(if、while、for、case、&&、||、?:、?? など)の数に 1 を加えたものとして計算されます。スコアが高いほど、メソッドの可読性、テスト容易性、安全な変更が困難になります。スコア 1 は単一の直線的なパスを意味し、約 10 は Thomas McCabe によって推奨された従来の上限であり、25 を超える値は Microsoft の CA1502 アナライザーによって過剰とフラグ付けされます。このガイドでは、C# の例を用いて、巡回的複雑度がどのように計算されるか、実務で重要な閾値は何か、実際の .NET コードベースでどのように測定・可視化するか、そして生のスコアを超えてテストカバレッジや IL レベルの分析と組み合わせる方法を説明します。
目次
巡回的複雑度とは?
巡回的複雑度は、1976 年に Thomas J. McCabe によって、コードの構造的複雑度を定量化する方法として導入されました。このアイデアはグラフ理論に由来します。各メソッドは、ノードがステートメントのブロックであり、エッジがそれらの間のジャンプである制御フローグラフとして表現できます。そのグラフ上で、巡回的複雑度は古典的な公式 M = E - N + 2P で与えられます。
M = E - N + 2P
ここで、E はエッジの数、N はノードの数、P は連結成分の数です。単一のエントリと単一のエグジットを持つ通常のメソッドの場合、これは 1 + 決定ポイントの数に単純化され、ほとんどのツールが実際に計算する形式です。この数値が本当に教えてくれるのは、メソッド内の各独立したパスを実行するために必要な最小テストケース数です。だからこそ、巡回的複雑度は半世紀近くも存続しているのです。それは構造的なメトリックですが、コードの保守やテストを行うすべての人にとって非常に具体的な運用上の意味を持っています。
C# における巡回的複雑度の定義
C# メソッドの巡回的複雑度は、具体的には 1 + {メソッド本体で見つかる以下の式の数} です。
if
while
for
foreach
case
default
continue
goto
&&
||
catch
三項演算子 (?:)
??
CC 計算に含まれない以下の式があります。
else
do
switch
try
using
throw
finally
return
オブジェクト生成
メソッド呼び出し
フィールドアクセス
人々がつまずく 2 つの詳細は次のとおりです。else は、その対応する if によって代替パスが既に作成されているため、スコアを増加させません。また、switch は、switch キーワード自体ではなく、case ごとに 1 ユニット(および default に 1)を寄与します。C# のパターンマッチング構文(and、or、パターンを使用した最新の switch 式)も、古典的な対応物と同じようにスコアに加算されます。
C# における巡回的複雑度の影響例
複雑なメソッドの提示
以下は、if と else のスコープが絡み合った複雑なメソッドです。if キーワードが 6 回、&& が 1 回使用されています。したがって、その巡回的複雑度スコアは 8 です。
C
public static class OrderLogic {
public static void ProcessOrder(
int orderId,
bool isPriority,
bool isInternational,
bool isGift,
bool isCouponApplied,
decimal orderTotal) {
if (orderId <= 0) {
Console.WriteLine("Invalid order ID.");
return;
}
if (isPriority) {
Console.WriteLine("Processing priority order.");
if (isInternational) {
Console.WriteLine("Processing international priority order.");
if (isGift) {
Console.WriteLine("This is a gift order.");
}
}
} else {
Console.WriteLine("Processing standard order.");
if (isInternational) {
Console.WriteLine("Processing international standard order.");
}
}
if (isCouponApplied && orderTotal > 100) {
Console.WriteLine("Applying discount for orders over $100.");
} else {
Console.WriteLine("No discount applicable.");
}
}
}
8 つの独立したパスということは、この単一メソッドを完全にカバーするために少なくとも 8 つのテストが必要であることを意味します。さらに、新しいビジネスルールを追加する必要があるたびに、かなりの頭痛の種となります。これは、回帰が unnoticed に入り込むまさにそのようなメソッドです。
複雑なメソッドのリファクタリングを複数の単純なメソッドに
上記のメソッドは、より小さく、より焦点を絞ったメソッドにリファクタリングできます。コードでは、各メソッドの巡回的複雑度スコアを CC と参照します。
C
public static class OrderLogic {
public static void ProcessOrder(
int orderId,
bool isPriority,
bool isInternational,
bool isGift,
bool isCouponApplied,
decimal orderTotal) {
// CC 2
if (!IsValidOrder(orderId)) return;
ProcessOrderType(isPriority, isInternational, isGift);
ApplyDiscountIfEligible(isCouponApplied, orderTotal);
}
private static bool IsValidOrder(int orderId) {
// CC 2
if (orderId <= 0) {
Console.WriteLine("Invalid order ID.");
return false;
}
return true;
}
private static void ProcessOrderType(
bool isPriority,
bool isInternational,
bool isGift) {
// CC 5
if (isPriority) {
Console.WriteLine("Processing priority order.");
if (isInternational) {
Console.WriteLine("Processing international priority order.");
}
} else {
Console.WriteLine("Processing standard order.");
if (isInternational) {
Console.WriteLine("Processing international standard order.");
}
}
if (isGift) {
Console.WriteLine("This is a gift order.");
}
}
private static void ApplyDiscountIfEligible(
// CC 3
bool isCouponApplied,
decimal orderTotal) {
if (isCouponApplied && orderTotal > 100) {
Console.WriteLine("Applying discount for orders over $100.");
} else {
Console.WriteLine("No discount applicable.");
}
}
}
単純な制御フローのリファクタリングの利点:
メインメソッドは、特定のタスクをより小さな、より焦点を絞ったメソッドに委譲するようになりました。
テストが容易: 各小さなメソッドを独立してテストできます。
低い巡回的複雑度: 複雑さが複数のメソッドに分散され、各メソッドの理解と保守が容易になります。