Coding Concepts! Cyclomatic Complexity

Chris Bertrand on September 17, 2018

Cyclomatic what? Even spell check doesn't recognise the word, but it's a super useful software metric for understanding how your software works. ... [Read Full]
markdown guide
 

I definitely like the write-up! I love to see software quality metrics being discussed. I do want to point out in your first example:

var msg = "";
if (month == 12 && day == 25) { // + 2
      msg = "Merry Christmas"; // +1
} else {
     msg = "Have a nice day"; // +1
}
return msg; // +1 - Total 5

Your suggested reduction in cyclomatic complexity (which I agree with) actually increases your essential complexity... though not above the structured/unstructured threshold. You add an additional exit point to the function, which is a trade off. Your second example:

switch (day) { // +1
                case 0: return "Sunday"; // +2
                case 1: return "Monday"; // +2
                case 2: return "Tuesday"; // +2
                case 3: return "Wednesday"; // +2
                case 4: return "Thursday"; // +2
                case 5: return "Friday"; // +2
                case 6: return "Saturday"; // +2 
                default: throw new Exception(); // +2 Total 17!
        }

The essential complexity is 8 because each case is an exit, as well as the default case. In this example, you don't need any trade-offs between cyclomatic and essential complexity; it's bad all the way around. The threshold we use on my quality team (based on McCabe) are that a method in OO code with an essential complexity of greater than 4 is considered to be unstructured.

Again, I'm definitely glad to see good editorials on software quality metrics. Thanks for the post!

 

"Your suggested reduction in cyclomatic complexity (which I agree with) actually increases your essential complexity... though not above the structured/unstructured threshold. You add an additional exit point to the function, which is a trade off. Your second example:
"

Additional exit point will not be added since the msg variable will have a default, then you only change the content of the variable when the if statement evaluate to true, and only one return statement is needed.

 
 

You're absolutely right. I was mixing code samples in my head. Thanks for the correction!

 

Thanks for the comment. I like the introduction to essential complexity. So long as the same code metrics are used throughout the codebase they should give an insight into code smells and areas ripe for refactoring!

 

Interesting. I assumed the topic would be more, well, complex.

One thing this method would seem to miss is that additional branches sometimes multiply the number of paths and sometimes don't.

# example one
# unique paths: 3
if a:
    if b:
        print('a and b')
    else:
        print('a and not b')
else:
    print('not a, but maybe b')

There are 3 possible paths through the above code, but the following code has four:

# example two
# unique paths: 4
if a:
    print('a and maybe b')
if b:
    print('b and maybe a')

However, the latter code's cyclomatic complexity would be lower. If I understand correctly, example one would have a cyclomatic complexity of 5, while example two would have a cyclomatic complexity of 3.

 

Hi Dustin, nested branches are definitely classed as more complex, have a look at your examples run through the Code Metrics extension in VSCode. The easiest way to gauge the count is to look at every possible route through the code, then add 1.

Complexity Pathway

 

Hmmm, the cyclomatic complexity M of a given function is M = E - N + 2, where E and N are the edges and nodes in the graph of its control flow. The control flows of both these examples contain 7 nodes and 8 edges, so both have a cyclomatic complexity of M = 8 - 7 + 2 = 3. This is confirmed by running radon on the Python version @cathodion offered.

control flow

 

Nesting does hurt readability, as the CQSE post you linked to mentions.

However, the first example has fewer routes because of the nesting. If a is False, there's only one route to take, regardless of the value of b.

It's about all potential routes through. Rather than specific routes. If you wanted to get 100% code coverage you would need to cover every possible route, this is what the complexity of trying to convey.

I'm not sure what you mean by potential vs. specific routes.

I think you're trying to say that it's either in the if or else statement. So there are two specific routes through. But those two routes vary massively depending on which it goes into. The metric doesn't gauge which is more likely or weight them accordingly. I agree It's definitely not perfect, the reverse piece in the additional reading section defines it's pitfalls well.

 

In the past, I used this vscode extension a lot. Some of my scripts had 500+ complexity and had to spend a lot of time to tune it down to 100. I learned a lot from that, however it was showing everywhere so I decided to frequently turn it off.

Great post.

 

This is one of the biggest advantage gained when using linters (static code analyzers).

Enforcing rules to keep the bad code away from the master branch!

 

Very true, they can be annoying at times, but a bit of tuning usually gets them configured to be useful for your methods.

 

Very useful! I didn't know about this concept. I will definitely add this to my tool box.

 
code of conduct - report abuse