- Blog
- McCabe's Cyclomatic Complexity and Why We Don't ...
In nearly every audience we present our analysis and quality management tool to, there is at least one person asking whether we also measure Cyclomatic Complexity. After all, almost every existing software quality tool calculates this metric and so it is very well known. Much to their surprise, our answer is usually no and I want to explain our rationale in this post.
Cyclomatic Complexity
Just to make sure we are talking about the same thing, Cyclomatic Complexity is a metric that was introduced by Thomas McCabe in 1976 and aims at capturing the complexity of a method in a single number. Actually, the original publication talks about the more general term module, but today this typically means function or method for most languages.
The metric itself is based on graph measures on the control flow graph of a method and describes the non-linearity of this graph. For structured programming (no goto) the metric is roughly equivalent to one plus the number of loops and if statements.
For example, the Cyclomatic Complexity of the following Java method is 3:
public static boolean containsLetter(String s) {
for (int i = 0; i < s.length(); i++) {
if (Character.isLetter(s.charAt(i))) {
return true;
}
}
return false;
}
If you want the full scientific treatment, you can find the original publication here.
Cyclomatic Complexity != Cyclomatic Complexity
Unless you prefer pen and paper for determining cyclomatic complexity, you will use one of the many existing tools for software metrics calculation out there. Unfortunately, the original paper is vague on some details of the metric, such as how to derive the control flow graph, and hence different implementations often result in different measured complexity values for the same code.
For example, the following simple Java code snippet is reported with complexity 2 by the Eclipse Metrics Plugin, with 4 by GMetrics, and with complexity 5 by SonarQube:
int foo (int a, int b) {
if (a > 17 && b < 42 && a + b < 55) {
return 1;
}
return 2;
}
The reason is that the conjunction in the condition is counted as separate branches in the control flow graph in one tool, while the other tool counts the entire if as one branch. The first behavior (value 4) is recommended in McCabe’s paper, but the later one is found in many tools as it is easier to implement (I have no explanation for the value 5, which seems to be too high).
Throw in new language features, such as the lambdas from C# and Java 8, and you are in for even more variations. So if you want to measure and compare complexity, stick with one of the tools and don’t copy thresholds from other sources blindly.
What do you want to measure?
Besides tools measuring different things for the same metric (which happens for other metrics as well), the main obstacle is the interpretation of the results. The simple interpretation is that the cyclomatic complexity is an upper bound for the number of test cases required to obtain branch coverage of the code. So, in the context of testing, cyclomatic complexity can be used to estimate the required effort for writing tests. In my opinion, this is a valid use-case for this metric.
Often, however, high “complexity” is directly translated to low readability and high maintenance costs. It turns out that complexity of the control flow graph of a piece of code is not necessarily what a human would perceive as complex.
For example, the following example results in a complexity value of 14, which is way beyond the threshold typically recommended for a single method.
String getMonthName (int month) {
switch (month) {
case 0: return "January";
case 1: return "February";
case 2: return "March";
case 3: return "April";
case 4: return "May";
case 5: return "June";
case 6: return "July";
case 7: return "August";
case 8: return "September";
case 9: return "October";
case 10: return "November";
case 11: return "December";
default:
throw new IllegalArgumentException();
}
}
Obviously, the complexity of the control flow graph is high and I would need many tests to completely test this method. But I feel fairly confident that I can understand and change that method without much effort even years after writing it (if I ever had to add a 13th month).
A more compelling example are the next two methods. Both have a cyclomatic complexity of 5, although most developers would judge the complexity of both methods to be very different.
// Method 1
String getWeight(int i) {
if (i <= 0) {
return "no weight";
}
if (i < 10) {
return "light";
}
if (i < 20) {
return "medium";
}
if (i < 30) {
return "heavy";
}
return "very heavy";
}
// Method 2
int sumOfNonPrimes(int limit) {
int sum = 0;
OUTER: for (int i = 0; i < limit; ++i) {
if (i <= 2) {
continue;
}
for (int j = 2; j < i; ++j) {
if (i % j == 0) {
continue OUTER;
}
}
sum += i;
}
return sum;
}
Both method have a CC of 5
But we want to measure complexity!
The sad truth is that there probably is no single simple measurement that can express an abstract concept such as complexity in a single number.
But this does not imply that we cannot measure and control complexity. It just has to be done with multiple metrics and checks that cover the various aspects of complexity.
For example, one very simple measure is the length of a method. While many refuse this metric as too simple, most agree that longer methods are harder to understand and maintain, making it a useful part of a set of metrics covering complexity.
Another metric we like to look at is nesting depth, which is the maximal number of control structures (loops, if) that are nested in each other. The method sumOfNonPrimes from the example above has nesting depth 3.
Nesting statements tends to expand the context that a developer has to understand for changing a line. One more benefit is that we can point a developer directly to a deeply nested statement instead of annotating an entire method with the label “too complex” and let the developer figure out where the complexity is hidden.
To complement general metrics, we typically employ checks for specific quirks of the languages. For example, you might want to discourage nesting of lambda expressions, or loops within switch statements.
I hope I could shed some light on the pitfalls when measuring complexity and would be happy to hear of your experience with complexity metrics in the comments.
Related Posts
Our latest related blog posts.