循環的複雑度は制御フローグラフのノード数nとエッジ数eから e - n + 2 という形で計算できます。 制御フローグラフに分岐が1つもない場合、この値は1となり、分岐が増えるごとに値が増えていくため、分岐の数 + 1という形で簡単に求めることができます。
今回の記事では、If, ElseIf, For, For Each, While, Case, Catch の数を数えて循環的複雑度を算出6することにします。 なお、IIf は一見三項演算子のようですが、扱いとしてはただの関数なので今回は対象としませんでした。自前で算出すると自由に計算ロジックを変えられるので、プロジェクトのルールに応じてカスタマイズできますね。
具体的には以下のようにカウントします。
PublicClass A ' 分岐の数は合計で10個なので循環的複雑度は11となる PublicSharedSub Main() Dim i AsInteger = 1 ' ここで+1 If i = 1Then Console.WriteLine("A") ' ここで+1 ElseIf i = 0Then Console.WriteLine("B") Else Console.WriteLine("C") EndIf
' ここで+1 While i > 0 i -= 1 EndWhile
' ここで+1 For index AsInteger = 0To1 Next index
Dim lst AsNew List(OfString) From {"A", "B", "C"} ' ここで+1 ForEach elem AsStringIn lst Console.WriteLine("{0}", elem) Next
Try ThrowNew Exception() ' ここで+1 Catch ex As Exception Console.WriteLine("Catch") EndTry
publicstaticboolIsDecisionNode(SyntaxNode node) { // Case Else は除外 if (node.IsKind(SyntaxKind.CaseElseStatement)) { returnfalse; }
return node is IfStatementSyntax || node is ElseIfStatementSyntax || node is WhileStatementSyntax || node is ForStatementSyntax || node is ForEachStatementSyntax || node is CatchStatementSyntax || node is CaseStatementSyntax || node is TernaryConditionalExpressionSyntax || node is BinaryConditionalExpressionSyntax; }
上記メソッドを用いて、メソッド毎の循環的複雑度は下記のように計算できます。
static Dictionary<string, int> CalcCyclomaticComplexityByQueryMethod(SyntaxTree syntaxTree) { var cyclomaticComplexityDict = new Dictionary<string, int>();
foreach (var methodBlockSyntax in syntaxTree.GetRoot().DescendantNodes().OfType<MethodBlockSyntax>()) { var methodStatementSyntax = methodBlockSyntax.ChildNodes().OfType<MethodStatementSyntax>().First(); var methodName = methodStatementSyntax.Identifier.Text; var methodCyclomaticComplexity = methodBlockSyntax.DescendantNodes().Where(node => CyclomaticComplexity.IsDecisionNode(node)).Count() + 1;