Coding Concepts! Cyclomatic Complexity

Chris Bertrand - Sep 17 '18 - - Dev Community

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

Having read about it a few years ago, it apparent use seems to have deteriorated. I feel it's a very valuable tool in a developer's arsenal, and something that should be used in code reviews and for the maintainability of your codebase. We all know to keep our code "Simple" We've all heard about the KISS principle, but were we ever told what simple really was, and how we should measure it?

Well this is where cyclomatic complexity enters the frame.

Image result for keep it simple stupid

Definition

Courtesy of wikipedia:

Cyclomatic complexity is a software metric, used to indicate the complexity of a program. It is a quantitative measure of the number of linearly independent paths through a program's source code. It was developed by Thomas J. McCabe, Sr. in 1976. Cyclomatic complexity is computed using the control flow graph of the program: the nodes of the graph correspond to indivisible groups of commands of a program, and a directed edge connects two nodes if the second command might be executed immediately after the first command. Cyclomatic complexity may also be applied to individual functionsmodulesmethods or classes within a program.

What that actually means

In essence it's the different number of routes through a piece of logic. To put it differently it's generally considered in the context of a maintainability index. The more branches there are within a particular function, the more difficult it is to maintain a mental model of its operation. The metric is roughly equivalent to one plus the number of loops and if statements. This is a pretty good view of how the value is configured, yes x may be bigger than 100 and move straight on, and that path through would have a complexity of 1, however the code block/ method itself has a score of 11.

Image result for cyclomatic complexity

Why should I care?

Code coverage is becoming an integral part of the development cycle. Cyclomatic complexity ultimately effects the amount of different unit tests you will need to write for a given piece of code. Additional routes through a method will require an additional test to be written, and an additional place your code could fall over, or bugs could crop up.

When you take this all into consideration, the cyclomatic complexity of your code ultimately determines its effectiveness, simplicity, maintainability and general practicality. So yeah pretty important, don't you think?

High “complexity” can be directly translated to low readability, and that also means it's more difficult for new developers coming in and understanding what is going on.

I'm sure you have had an experience of looking at some code, where you had no idea what was going on, and why something was written "that" way.

So next time you write something, bear that in mind, that the next person looking at it might not be you. Leave it in such a state that you would be happy finding it in. This approach has always helped me when finishing up a new feature.

How important is Cyclomatic Complexity?

Who doesn't love a good table, this one shows what the different values a method could have and what that means?

Complexity What IT MEANS
1-10 Structured and well written code that is easily testable.
10-20 Fairly complex code that could be a challenge to test. Depending on what you are doing these sorts of values are still acceptable if they're done for a good reason.
20-40 Very complex code that is hard to test. You should look at refactoring this, breaking it down into smaller methods, or using a design pattern.
>40 Crazy code, that is not at all testable and nearly impossible to maintain or extend. Something is really wrong here and needs to be scrutinised further.

These different levels help us better understand the code we are writing and the implication this will have on any testing resource we may need. It also makes us aware that any high levels of complexity will cause us problems in the future and time should be spent on refactoring this at the next available opportunity.

What can we do to fix this?

The table above shows the different levels of complexity and when you should start looking at refactoring your code. We'll have a look at a few ways this can be achieved. By far the easiest way is to remove any un-needed if or else statements. These sometimes crop up during development and then don't get removed. One common example you may find in your code base goes like this.

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

There doesn't look a lot wrong with the code above. However, if we simply remove the else statement and move the default message to the declaration we remove 1 complexity point straight away. It's an easy change and one that is common.

Another big culprit for high complexity are case or switch statements.

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!
        }

In certain cases, you can't get away from blocks of code as above, that's what they were designed for. But sometimes switch statements are just bad code design. The strategy pattern is a good approach to take if your switch statement is likely to increase. In the example above it is unlikely we are going to get new days added to our calendar, but take for example:

switch (carGarage) {
case 'seat': return contactSeat(); // +2
case 'audi': return contactAudi(); // +2
default: return contactFord();  // +2 - Total 6
} 

We have 3 case statements here, but one could expect that to expand, looking at what it's currently implementing. Adding additional case statements is one possible solution for expanding this code,  but that will increase the complexity with each additional case! A strategy pattern would tidy this up nicely.

enum CarDealerTypes { Seat, Audi, Ford }

interface CarDealerStrategy {
    CallDealer();
}

class SeatDealer implements CarDealerStrategy {
    CallDealer() {
        CallSeat(); // +1
    }
}

class AudiDealer implements CarDealerStrategy {
    CallDealer() {
        CallAudi(); // +1
    }
}

class FordDealer implements CarDealerStrategy {
    CallDealer() {
        CallFord(); // +1
    }
}

class Dealership {
    // Here is our alternative to the case statements, easy right!
    CallDealer(dealer: CarDealerStrategy) {
        dealer.CallDealer(); // +1
    }

    // These are the methods that will ultimately be used
    ContactAudiDelership() {
        this.CallDealer(new AudiDealer()); // +1
    }
}

It's a higher setup cost, and slightly more complicated to begin with. However, after you're 15 switch statement is added, you'll be happy you decided to switch approaches! In addition, we have improved the complexity from the original 3 in the case statement to 1 in the Strategy pattern. Imaging if your switch statement was doing additional logic, and had additional if statements embedded, you can see this becoming a real struggle to test!

Use that noggin!

Image result for use that noggin gif

As with everything development related, the most important thing to keep in mind is that you don't just change stuff because you want to.

Refactoring and improving your codebase is imperative to keeping a clean and concise environment. If you find your code is running smoothly, not causing you or your customers any issues, then don't change it because a code metric is telling you it's wrong.

Code is legacy the moment it is written, so your refactoring might be obsolete during the next round of development. Improve the code if it is being amended anyway. A good programmer should fix any issues they find whilst working on a story or feature, but not changing code that will require additional testing that doesn't directly affect what they are currently doing.

Tools

So you understand the concept, you understand how to fix it, but what's the easiest way to figure out what's a potential problem! Well most IDE's should offer some in built tools to help you out. I'll run through a couple now:

Visual Studio 

Simply calculate your code metrics by going to Analyze | Calculate Code Metrics for Solution. Find more details here: Visual Studio - Code Metric Help

VsCode

I've linked to a great extension that I've been using recently, this will display the complexity at the top of the function! Find it here: CodeMetric Extension

There are tools out there for most IDE's so go out there and find the one that suits you!

I hope this introduction into Cyclomatic Complexity gives you something to think about, and helps you sometime in the future. The additional reading below delves further into the subject so feel free to have a read if this topic interest you further. As always, let us know what you think in the comments below.

This was originally posted on my own blog here: Design Puddle Blog - Coding Concepts- Cyclomatic Complexity


Additional Reading

McCabes full thesis: http://mccabe.com/pdf/mccabe-nist235r.pdf

A different perspective, why you shouldn't use it? https://www.cqse.eu/en/blog/mccabe-cyclomatic-complexity/

And some more clarification: https://dzone.com/articles/what-exactly-is-mccabe-cyclomatic-complexity

Terabox Video Player