Cyclomatic Complexity - What it is, why you should care, and how to reduce it using the Strategy Pattern

July 10, 2022

Cyclomatic complexity is one of those terms that makes you think you missed something when you were learning programming. However, the concept is a really simple one. Cyclomatic complexity is simply the number of paths through your code. There are more detailed explanations if you scan the web (involving edges and nodes), but for this post, we’ll just work with that.

Code Metrics

This article is not about .Net per se - what I’m writing here applies to any OO language; however, for the purpose of illustration, I’ll be using C#, and the Code Metrics Window in Visual Studio.

Cyclomatic Complexity

Given what we’ve just said, we can take the following program:



int x = 1;

And we can determine that the cyclomatic complexity is 1 - that is, there is only one way that this code can execute - no branches, no loops, just one statement. So, the cyclomatic complexity is 1:

Cyclomatic complexity

Okay, let’s now add a single branch:

Cyclomatic complexity

There are now two ways this code can execute (in actuality, there is only one, but cyclomatic complexity doesn’t follow the actual logical course - so let’s agree on two for the sake of argument).

We can now add a second condition:

Cyclomatic complexity

The complexity now goes to three.

Okay, so that’s all very interesting, but what does that actually mean - why is it useful to know this number? The main answer to this is testability: if you know there are 3 possible execution paths for the code, then you know that you need a minimum of 3 tests to cover those paths. There’s a second part to that, which is that a method that requires one - three tests might be easy to change or debug, one that requires ten - twenty tests is definitely not.

So, the question is, how can we reduce this figure?

Reducing Cyclomatic Complexity

There are, obviously, more answers to this than would fit in this post, but here I’m going to focus on bringing in a strategy pattern.

Let’s consider the following code:




    // Night
    switch (thingToAutomate)
    {
        case "Door":
            Console.WriteLine("Lock");
            break;

        case "Window":
            Console.WriteLine("Close");
            break;

        case "TV":
            Console.WriteLine("Turn off");
            break;

        case "Lights":
            Console.WriteLine("Turn off");
            break;
    }


This method had a cyclomatic complexity of 5 - there are 4 options, but also the possibility that none are true.

The essence behind the strategy pattern is just that you assign functionality using polymorphism. If we consider the code above, doing that would actually increase the number of lines of code, and thereby increase the cyclomatic complexity. However, what if, in addition to the automation routine at night, we needed one for the morning:



// Morning
switch (thingToAutomate)
{
    case "Door":
        Console.WriteLine("Unlock");
        break;

    case "Window":
        Console.WriteLine("Open");
        break;

    case "TV":        
    case "Lights":        
        break;
}

Okay, now our cyclomatic complexity increases (to 9) - and will increase every time we need to vary our behaviour based on the thing that we’re automating. Instead, let’s consider the following:



    internal interface IThingToAutomateStrategy
    {
        void Morning();
        void Night();
    }

Let’s now imagine that we implement the interface for each thing:



    internal class Door : IThingToAutomateStrategy
    {
        public void Morning()
        {
            Console.WriteLine("Unlock");
        }

        public void Night()
        {
            Console.WriteLine("Lock");
        }
    }

    . . .

Admittedly, this does increase the lines of code, but we end up with simpler code, and it has a lower, overall, cyclomatic complexity:



    internal class Automation
    {
        public IThingToAutomateStrategy AutomateStrategy { get; set; }

        public void AutomateMorning()
        {
            AutomateStrategy.Morning();            
        }

        public void AutomateNight()
        {
            AutomateStrategy.Night();
        }
    }

We can then use this in our program like this:



IThingToAutomateStrategy automateStrategy;

switch (thingToAutomate)
{
    case "Door":
        automateStrategy = new Door();
        break;

    case "Window":
        automateStrategy = new Window();
        break;

    case "TV":
        automateStrategy = new TV();
        break;

    case "Lights":
        automateStrategy = new Lights();
        break;
}

automateStrategy.Morning();
automateStrategy.Night();


The cyclomatic complexity of this is back to 5; but the best part is that it doesn’t increase. Imagine the following new method:



    internal interface IThingToAutomateStrategy
    {
        void Morning();
        void Darkness();
        void Night();
    }

And the implementation:



    internal class Lights : IThingToAutomateStrategy
    {
        public void Darkness()
        {
            Console.WriteLine("Switch on");
        }

        public void Morning()
        {
            // Nothing to do here
        }

        public void Night()
        {
            Console.WriteLine("Turn Off");
        }
    }

It’s worth pointing out that we’re in breach of the ISP here - but since we’re only doing it to make a point, we’ll agree to let it slide.

Adding this to the code flow doesn’t affect the cyclomatic complexity score of that code file at all:



IThingToAutomateStrategy automateStrategy;

switch (thingToAutomate)
{
    case "Door":
        automateStrategy = new Door();
        break;

    case "Window":
        automateStrategy = new Window();
        break;

    case "TV":
        automateStrategy = new TV();
        break;

    case "Lights":
        automateStrategy = new Lights();
        break;
}

automateStrategy.Morning();
automateStrategy.Darkness();
automateStrategy.Night();


It’s worth noting that it does increase the overall complexity, as it counts as a single additional code path per thing.

References

https://docs.microsoft.com/en-us/visualstudio/code-quality/code-metrics-cyclomatic-complexity?WT.mc_id=DT-MVP-5004601

https://docs.microsoft.com/en-us/visualstudio/code-quality/how-to-generate-code-metrics-data?WT.mc_id=DT-MVP-5004601

https://codinghelmet.com/articles/reduce-cyclomatic-complexity-composite-design-pattern

https://www.c-sharpcorner.com/UploadFile/shinuraj587/strategy-pattern-in-net/

https://codewithshadman.com/strategy-pattern-csharp/

https://github.com/pcmichaels/StrategyExample



Profile picture

A blog about one man's journey through code… and some pictures of the Peak District
Twitter

© Paul Michaels 2024