Home Code Quality An In-Depth Explanation of Code Complexity

An In-Depth Explanation of Code Complexity

Author

Date

Category

It’s no secret code is a complicated thing to write, debug, and maintain which is necessary for high software quality. Moreover, high code complexity brings with it a higher level of code defects, making the code costlier to maintain.

So, by reducing code complexity, we can reduce the number of bugs and defects, along with its lifetime cost. What exactly is complex code? How can we objectively assess how complex a piece of code is, whether that’s an entire codebase or one small function?

In this article, I’m going to walk through three complexity metrics for assessing code complexity. These are:

  • Cyclomatic complexity
  • Switch statement and logic condition complexity
  • Developer skill

I’ll also go through some of the benefits of assessing and understanding code complexity.

Cyclomatic Complexity

In 1976, Thomas McCabe Snr proposed a metric for calculating code complexity, called Cyclomatic Complexity. It’s defined as:

A quantitative measure of the number of linearly independent paths through a program’s source code…computed using the control flow graph of the program.

If you’re not familiar with a Control Flow Graph:

It is a representation, using graph notation, of all paths that might be traversed through a program during its execution.

Said more straightforwardly, the fewer the paths through a piece of code, and the less complex those paths are, the lower the Cyclomatic Complexity. As a result, the code is less complicated. To demonstrate the metric, let’s use three, somewhat arbitrary, Go code examples.

Example One

func main() {
    fmt.Println("1 + 1 =", 1+1)
}

As there’s only one path through the function, it has a Cyclomatic Complexity score of 1, which we can find by running gocyclo on it.

Example Two

func main() {
    year, month, day := time.Now().Date()
    if month == time.November && day == 10 && year == 2018 {
        fmt.Println("Happy Go day!")
    } else {
        fmt.Println("The current month is", month)
    }
}

In this example, we’re retrieving the current year, month, and day. With this information, we then check if the current date is the 10th of November 2018 with an if/else condition.

If it is, then the code prints “Happy Go day!” to the console. If it isn’t, then it prints “The current month is” and the name of the current month. The code example is made more complicated as the if the condition is composed of three sub-conditions. Given that, it has a higher complexity score of 4.

Example Three

func main() {
    _, month, _ := time.Now().Date()

    switch month {
    case time.January:
        fmt.Println("The current month is January.")
    case time.February:
        fmt.Println("The current month is February.")
    case time.March:
        fmt.Println("The current month is March.")
    case time.April:
        fmt.Println("The current month is April.")
    case time.May:
        fmt.Println("The current month is May.")
    default:
        fmt.Println("The current month is unknown.")
    }
}

In this example, we’re printing out the current month, based on the value of month, retrieved from the call to time.Now().Date(). There are seven paths through the function, one for each of the case statements and one for the default.

As a result, its Cyclomatic Complexity is 7. If we’d accounted for all the months of the year, along with a default, however, its score would be fourteen. That happens because Gocyclo uses the following calculation rules:

1 is the base complexity of a function
+1 for each ‘if’, ‘for’, ‘case’, ‘&&’ or ‘||’

Using these three examples, we can see that by having a standard metric for calculating code complexity, we can quickly assess how complex a piece of code is.

We can also see how different complex sections of code are in comparison with each other. However, Cyclomatic Complexity is not enough on its own.

Switch Statement and Logic Condition Complexity

The next assessor of code complexity is the switch statement and logic condition complexity. In the code example below, I’ve taken the second Go example and split the compound if condition into three nested conditions; one for each of the original conditions.

func main() {
    year, month, day := time.Now().Date()
    output := fmt.Sprintf("The current month is %s", month)

    if month == time.November {
        if day == 13 {
            if year == 2018 {
                output = fmt.Sprintf("Happy Go day!")
            }
        }
    }

    fmt.Println(output)
}

Which is easier to understand (or less complicated), the original one, or this one? Now let’s build on this, by considering the following three questions.

  • What if we had, as we do above, multiple if conditions and each one was quite complex?
  • What if we had multiple if conditions and the code in the body of each one were quite complex?
  • Would the code be easier or harder to understand?

It is fair to say that the greater the number of nested conditions and the higher the level of complexity within those conditions, the higher the complexity of the code.

Software Developer Skill Level

What about the skill level of the developer? Have a look at the C version of the second Go example below.

#include <stdio.h>
#include <time.h>
#include <string.h>

int main()
{
  time_t t = time(NULL);
  struct tm tm = *localtime(&t);
  const char * months[12] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};

  if (tm.tm_year == 2018 &&
      strncmp(months[tm.tm_mon], "November", strlen(months[tm.tm_mon])) == 0
      && tm.tm_mday == 10)
  {
    printf("Happy C Day!.\n");
  } else {
    printf("The current month is %s.\n", months[tm.tm_mon]);
  }
}

Technically, it does what the other examples do. However, it requires more code to achieve the same outcome. To be fair, if I had a greater familiarity with C, the code might be no longer than the Go example.

However, let’s say this is the minimum required to achieve the same outcome. If you compare the two, given the more verbose nature of C’s syntax when compared to Go, it’s harder to understand.

What’s more, if you had no prior experience with C, despite a comparatively similar Cyclomatic Complexity score, what would your perception be?

Would you consider the code to be less or more complicated? So this is another essential factor in understanding code complexity.

The Benefits of Measuring Software Complexity

There are four core benefits of measuring code complexity, plus one extra.

Better Tests

By knowing how many independent paths there are through a piece of code, we know how many paths there are to test.

I’m not advocating for 100% code coverage by the wayโ€”that’s often a meaningless software metric. However, I always advocate for as high a level of code coverage as is both practical and possible.

So, by knowing how many code paths there are, we can know how many paths we have to test. As a result, you have a measure of how many tests are required, at a minimum, to ensure that the code’s covered.

Reduced Risk

As the old saying goes:

It’s harder to read code than to write it.

What’s more:

  1. Code is read far more than it is written
  2. A good software developer should never be assessed by the lines of code they’ve written (or changed), but by the quality of the code they’ve maintained.

Given that, by reducing code complexity, you reduce the risk of introducing defects; whether they’re small or large, slightly embarrassing or bankruptcy-inducing.

Lower Costs

When the risk of potential defects is reduced, there are fewer defects to findโ€”and remove. As a result, the maintenance cost also reduces.

We’ve all seen and are familiar with the costs associated with finding defects at the various stages in a software’s life, as exemplified in the chart below.

Rising cost of defects

So it makes sense that, if we understand the complexity of our code, and which sections are more complicated than others, then we are in a far better position to reduce said complexity.

So by reducing that complexity, we reduce the likelihood of introducing defects. That flows into all stages of a software’s life.

Greater Predictability

By reducing software complexity, we can develop with greater predictability. What I mean by that is we’re better able to sayโ€”with confidenceโ€”how long a section of code takes to complete. By knowing this, we’re better able to predict how long a release takes to ship.

Based on this knowledge the business or organization is better able to set its goals and expectations, especially ones that are directly dependent on said software. When this happens, itโ€™s easier to set realistic budgets, forecasts, and so on.

Helps Developers Learn

Helping developers learn and grow is the final benefit of understanding why their code is considered complex. The tools I’ve used to assess complexity up until this point don’t do that.

What they do is provide an overall or granular complexity score. However, a comprehensive code complexity tool, such as Codacy, does.

File complexity list

In the screenshot above, we can see that, of the six files listed, one has a complexity of 30, a score usually considered quite high.

Cyclomatic complexity is a great indicator to understand if code quality deteriorating for any given change.ย Cyclomatic complexity can be harder to reason when looking at it or comparing whole modules given its infinite scale and not being related to the module size. However, something that you might find useful is looking at Codacyโ€™s File listย sorted by priority, which will help you understand which files are candidates of poor code quality, and then by consequence their modules.

Book demo banner - Join over 200 000 developers shipping high-quality code with Codacy

That’s a Wrap

Also, this has been an in-depth discussion about what code complexity is, how it’s assessed, as well as the significant benefits of reducing it. While there is more to understanding code complexity than I’ve covered here, we’ve gone a long way to understanding it.

If this is your first time hearing about the term or learning about any of the tools, I encourage you to explore the linked articles and tools, so that you learn more. If you don’t code in Go or C, then google “code complexity tool” plus your software language(s). You’re sure to find many tools available.

For more tips to improve code quality check out some other blog posts from Codacy.

Finally, if you want a comprehensive tool for assessing code quality, and one that helps your developers learn and grow, then try out Codacy.


Codacy is used by thousands of developers to analyze billions of lines of code every day!

Getting started is easy โ€“ and free! Just use your  GitHub, Bitbucket or Google account to sign up.

GET STARTED

9 COMMENTS

  1. I really enjoyed your article. I found it useful as I was thinking through some issues related to code complexity. However, I think there is a significantly less complicated solution in C. One deficiency of C is the lack of easily accessible month-words in the standard library, so the lookup of for the month-word string is unavoidable. However, you can make the date comparison for C-Day with far less work by comparing two time values. I’ve provided an implementation below.

    https://gist.github.com/rdammkoehler/a60693f6d8128182f4afce208a1089e2

    • Hi Hans, thank you for reachint out. The post on dzone is the exact copy from this article, which dzone does not allow any branding or mention of the company who writes it but I’d like to confirm with you that this is the original article. Thank you!

  2. Hello, really informative article.
    I am a little confused on “Example Three”, as there is 5 cases and a default case in the code. But the describtion is counting them as 7.

    So is there 6 or 7 cases?
    And is that then also the complexity just for the paths of the switch case?
    Or am I way off?

    • 1 is the base complexity of a function – this is stated in this article.

      each case adds 1, i think we get to 6 because the ‘default’
      case of the switch statement is also a case.

      so 6 + 1 = 7

    • The base complexity score of a function is 1, as stated in this article

      So adding the 5 cases and the default we get 7

  3. Hi! Is there some good-practice number that should suit most codebases, languages?

    I used to work in a place that had a linting rule of cyclomatic complexity of 2 for functions. It resulted in rly simple short funcs but also provided challenges for reading as some more complex pieces of code was just split to sub-functions to pass complexity rule and they were only used in 1 place really.

    What are your thoughts on that.

    • Helle there!

      The main idea should be to reduce the cyclomatic complexity without compromising understandability that much. After all, your code needs to be easily understandable by other developers ๐Ÿ™‚

      As a rule of thumb, you can use 4 as a good cyclomatic complexity, but between 5 and 7 should be acceptable if it makes the code more readable.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Subscribe to our newsletter

To be updated with all the latest news, offers and special announcements.

Recent posts

How does code quality fit into your CI/CD pipeline?

Continuous Integration and Continuous Deployment (CI/CD) are key for organizations wanting to deliver software at scale. CI/CD allows developers to automate...

How Stim uses Codacy to achieve high-quality code

We spoke with Tobias Sjรถsten, Head of Software Engineering at Stim, about how Codacy helps them guarantee code quality and standardization...

6 things developers should do to ship more secure code

Writing better, more secure source code is fundamental to prevent potential exploits and attacks that could undermine your software applications. However,...

Best practices for security code reviews

In today's interconnected world, where data breaches and cyber threats are increasingly common, one of your top priorities should be to...

April Product Update ๐Ÿš€

Hi there ๐Ÿ‘‹ It's been a whirlwind month, and we have big news to share: