Visual Studio Code Metrics Cyclomatic Complexity & LINQ
Check about Cyclomatic Complexity.
How does it work in Visual Studio?
* CC = Cyclomatic Complexity
{
return number;
}
In the previous code fragment CC = 1 = one execution path.
Next, having two possible execution paths (inline conditionals “?” affect control flow) .
{
bool condition = number > 10;
return condition ? 1 : -1;
}
Next, short circuiting boolean expressions generate additional execution paths:
{
bool condition = number > 10 && number < 20;
return condition ? 1 : -1;
}
The previous is a cool example of how execution branches could be very subtle. Short circuiting evaluation avoids executing code fragments that are not necessary (the first term in the “And” expression is False, thus not second term evaluation is not required).
A Bug may arise when executing those Terms not regularly evaluated due to Short Circuiting:
{
bool condition = number > 10 && MethodWithABug() < 20;
return condition ? 1 : -1;
}
{
bool condition = number > 10 && number < 20 && DateTime.Now.Year == 2012;
return condition ? 1 : -1;
}
Keep adding branches and CC will keep growing:
{
bool condition = number > 10 && number < 20 && DateTime.Now.Year == 2012;
if (condition)
{
CallSomeOtherMethod();
}
return condition ? 1 : -1;
}
Loops add cycling execution paths, thus they contribute to CC as well.
{
bool condition = number > 10 && number < 20 && DateTime.Now.Year == 2012;
if (condition)
{
CallSomeOtherMethod();
}
int[] numbers = GetNumbers();
foreach (var item in numbers)
{
DoSomethingWithNumber(item);
}
return condition ? 1 : -1;
}
Loop with a filtering “if”.
{
bool condition = number > 10 && number < 20 && DateTime.Now.Year == 2012;
if (condition)
{
CallSomeOtherMethod();
}
int[] numbers = GetNumbers();
foreach (var item in numbers)
{
if (item % 2 == 0)
DoSomethingWithEvenNumber(item);
}
return condition ? 1 : -1;
}
Compare the previous to using LINQ.
{
bool condition = number > 10 && number < 20 && DateTime.Now.Year == 2012;
if (condition)
{
CallSomeOtherMethod();
}
int[] numbers = GetNumbers();
foreach (var item in numbers.Where(i=> i % 2 == 0))
{
DoSomethingWithEvenNumber(item);
}
return condition ? 1 : -1;
}
Could you guess Why CC is grater in this case? Hint: check the IL!
When using LINQ, generic IEnumerator<T> is used instead. Generic enumerators implement IDisposable interface:
Thus, generated IL is adding a Try/Finally block in order to Dispose the enumerator. In the Finally block, the compiler generates code to compare the enumerator against null and disposing it.
{
if (enumerator != null)
((IDisposable)enumerator).Dispose();
}
This adds 1 point to the metric (“if” control flow).
The other extra point is due to the cache mechanism used by C# compiler when generating anonymous delegates. If we check the IL we’ll see an extra conditional branch checking for cached delegates.
We could assume the generated code is relative Bug free. The latter is not entirely true, imagine if we would have implemented a custom Enumerator with a custom Dispose. Now imagine you have a Bug in the Dispose pattern :( .