Today I was introduced to the term "
Cyclomatic Complexity". I'd not heard the term before but was familiar with the concept - now, though I wanted to learn more about how testing can be involved and whether this
metric would be a good way to help identify risk in an application.
On
R. Kevin Cole's website I found information I thought to be valuable and wanted to include it here. Excerpts from his 2007 post follow (bolded for emphasis):
-----------------------------
Cyclomatic complexity is a static software metric pioneered in the 1970s by Thomas McCabe. The cyclomatic complexity number (CCN) is a measure of how many paths there are through a method. It serves as a rough measure of code complexity and as a count of the minimum number of test cases that are required to achieve full code-coverage of the method.
Cyclomatic complexity measures the number of execution paths through a method; therefore, every method has, at a minimum, a cyclomatic complexity of 1 since there is at least one path through the method. This means that even the simplest getter/setter method has CCN = 1:
public String getName()
{
return this.name;
}
In the following method there are two decision points. Remembering that every method has at least a CCN value of 1, the final value for the cyclomatic complexity of the getResult(...) method is 3.
public int getResult(int p1, int p2)
{
int result = 0;
if (p1 == 0)
{
result += 1;
} else
{
result += 2;
}
if (p2 == 0)
{
result += 3;
} else
{
result += 4;
}
return result;
}
Conditionals and loops add to the complexity of a method. Each additional if
, case
, while
, etc, adds 1 to your CCN score because you're adding another potential path through the method.
In general,
- add 1 for each
if
statement.
- add 1 for each
for
statement.
- add 1 for each
while loop
- add 1 for each
do-while loop
.
- add 1 for each
&&
(an implied if statement).
- add 1 for each
||
(an implied if statement).
- add 1 for each
?
(an implied if statement).
- add 1 for each
.
(an implied if statement).
- add 1 for each
case
statement.
- add 1 for each
default
statement.
- add 1 for each
catch
statement.
- add 1 for each
finaly
statement.
- add 1 for each
continue
statement.
...
The number of execution paths through a method is directly related to the understandability, maintainability, and testability of the method. A general rule of thumb states that in order to ensure a high level of test coverage, the number of test cases for a method should be at least equal to the method's cyclomatic complexity value. When the number of test cases is equal to the CCN value, you can feel confident that your tests have followed every path through your code.
The getResult(...) method above would require at least four test cases to achieve complete code coverage. The following table illustrates the argument values required to test each path through the method.
Test # | p1 | p2 |
1 | 0 | 0 |
2 | 0 | not 0 |
3 | not 0 | 0 |
4 | not 0 | not 0 |
Another rule of thumb when considering the CCN value is the relationship between CCN and risk:
Cyclomatic Complexity | Risk Summary |
1-10 | Simple, low risk |
11-20 | Moderate complexity, medium risk |
21-50 | Complex, high risk |
51+ | Very high risk |
Studies have shown that
bug counts spike in methods with CCN > 13. A method with a CCN value greater than 10 is considered complex. By determining the cyclomatic complexity of various class methods and paying attention to outlier values, one can uncover code that may be a candidate for a testing and/or refactoring effort. The higher the value of CCN for a given method, the harder it is to test.
-----------------------------
I couldn't find the study that R. Kevin Cole references, but McCabe recommended keeping the CCN at 10 or under. If nothing else, bringing this number to the front of some of my analysis will help drive conversation with the developers with whom I work. In the meantime, it'll also be a great metric to help me assess my own code's complexity and determine if there's a need to simplify.