Press "Enter" to skip to content

Month: February 2017

Cyclomatic Complexity in Visual Studio

This lesson will demonstrate how to calculate cyclomatic complexity – which can help you find areas in your program that could use some improvements.

 

Video version here

 

What is Cyclomatic Complexity?

Cyclomatic Complexity (I’ll use “CC”, to abbreviate it, in places) is the number of potential execution paths (codepaths) that exist in a function.

If a function does not have any conditional statements (“if”, “else”, “case”, etc.), there is only one possible path through the function. So, it has a cyclomatic complexity of 1.

Each “if”, “else”, “case”, etc. produces another potential path through a function, and increases the cyclomatic complexity count by 1.

This gives you an approximate idea of how complex a function is – more possible paths, equals more complex.

 

Why is Cyclomatic Complexity important?

A computer does not have a problem running a function with 100+ paths through it. The problem is for us humans.

If a function has a CC of more than around 30, it starts to become difficult to understand what it is doing – at least, it usually does for me. In my experience, code with high cyclomatic complexity is usually the code with the most bugs. And, if I don’t understand what a function is doing, it is extremely difficult to fix bugs in it, or modify it.

So, when I see a function with a CC over 30, I start to look for ways to simplify it, or to break it into smaller functions.

 

NOTE: An important thing to remember is that cyclomatic complexity is only an indicator of a possible problem. A “switch” statement with many “case” options would have a high CC. However, that might also be the simplest way to write that function.

 

How to manually measure Cyclomatic Complexity

I’ll use this Employee class to demonstrate. To manually determine a function’s cyclomatic complexity, you need to count each potential branch through it. You can do this by counting the number of conditional statements (“if”, “else”, and “case”), and adding 1 (the “default” path through the function).

using System;
namespace Engine
{
    public class Employee
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public DateTime HireDate { get; set; }
        public bool IsStillEmployed { get; set; }
        public decimal Salary { get; set; }
        public void IncreaseSalary_Short(decimal increasePercentage)
        {
            Salary = Salary * (1 + (increasePercentage / 100));
        }
        public void IncreaseSalary_Long(decimal increasePercentage)
        {
            decimal percent = increasePercentage / 100;
            decimal increase = Salary * percent;
            decimal newSalary = Salary + increase;
            Salary = newSalary;
        }
        public string EmploymentStatus_Short()
        {
            return IsStillEmployed ? "Active" : "Former";
        }
        public string EmploymentStatus_Long()
        {
            if(IsStillEmployed)
            {
                return "Active";
            }
            else
            {
                return "Former";
            }
        }
        public void IncreaseSalary_Complex(decimal increasePercentage)
        {
            if(IsStillEmployed)
            {
                TimeSpan timeEmployed = DateTime.Now - HireDate;
                if(timeEmployed.TotalDays >= 180)
                {
                    // People employed more than 180 days can receive a full increase
                    Salary = Salary * (1 + (increasePercentage / 100));
                }
                else if(timeEmployed.TotalDays >= 90)
                {
                    // People employed 90-179 days can only receive a half increase
                    Salary = Salary * (1 + ((increasePercentage / 2) / 100));
                }
            }
        }
    }
}

 

The IncreaseSalary_Short function has one possible execution path through it. Every call to that function will run the one line in it. So, it has a CC of 1.

 

IncreaseSalary_Long has more lines of code than IncreaseSalary_Short. However, it also has only one path through it. All the lines will be run, every time the function is called. This function also has a CC of 1.

 

EmploymentStatus_Short is only one line long, but that line includes a conditional branch – the “?” in the statement, which can be true or false.

There are two possible paths through that function. One if “IsStillEmployed” is true, and the second, if “IsStillEmployed” is false. So, this function has a CC of 2.

 

EmploymentStatus_Long has the same logic as EmploymentStatus_Short. However, it uses a traditional “if” “else” statement, instead of using the ternary operator. Even though it has more lines of code, there are still 2 paths through the function, and it has a CC of 2 – just like EmploymentStatus_Short.

 

IncreaseSalary_Complex has four possible paths through it – which gives it a CC of 4.

The four paths are:

IsStilEmployed = false

IsStillEmployed = true, and timeEmployed greater than 180

IsStillEmployed = true, and timeEmployed between 90 and 179

IsStillEmployed = true, and timeEmployed less than 90

 

Tools to measure Cyclomatic Complexity

Visual Studio Community 2015 (and some other versions) can compute cyclomatic complexity for you.

From Visual Studio’s menu, select Analyze -> Calculate Code Metrics. Then, either select “For Solution”, if you want check all the projects in the solution, or select the project you want.

Visual Studio Menu -> Analyze

The results will be displayed in the Output window.

Visual Studio - Code Analysis Output

Visual Studio shows the cyclomatic complexity for each function and property. It also totals those values by class/namespace/project.

Personally, I don’t think the class/namespace/project-level totals are very useful. If the individual functions are small, and have a low CC, the classes are usually easy to understand.

The code analysis also includes some other useful numbers: maintainability index (a general quality score), depth of inheritance (fewer layers of inheritance is usually better), class coupling (less coupling makes it easier to unit test individual classes), and lines of code.

You can also click on the Excel icon, to export the CC results to an Excel spreadsheet.

 

Another tool to use to calculate cyclomatic complexity is NDepend. It’s a commercial product, and is not cheap. A developer license costs US$ 425, and a build server license costs US$ 850. So, it’s probably something you’ll only want to get if you are developing large projects – or obsessed about code quality (like I am).

Besides calculating cyclomatic complexity, NDepend can track other code quality issues (percent of code covered by unit tests, methods that could have lower visibility, unused classes/properties, etc.).

It can record these values over time. So, you’ll know if your code quality is getting better or worse.

My favorite feature of NDepend is the Cyclomatic Complexity heat map. This gives you an easy way to demonstrate to non-technical people the code you probably need to refactor.

The red areas (methods/classes/namespaces) are the parts of the code with a higher CC. These are usually the best areas to perform refactoring.

NDepend Cyclomatic Complexity Heatmap

If possible, add a tool like this to your continuous integration server. Run it nightly, and use it to identify areas of your solution that might need improvement.

 

If you’re interested in knowing more about NDepend, let me know. I can go into more details about it in another post. Or, you can download a copy for a 14-day free trial, and run it against some of your existing projects.

 

 

Cyclomatic Complexity Summary

In my experience, programs have long lives. I frequently re-use code from projects I created many years ago. I wrote a corporate web app 17 years ago, and it looks like it’s still in use.

Time invested in maintaining high-quality code can have a massive return. And, regularly measuring cyclomatic complexity can help you know when it’s time to clean up (refactor) code.

And now that Visual Studio has this as a built-in feature, it’s easy to do.

If you’re writing a small, personal project, you usually don’t need to worry about cyclomatic complexity.

However, it’s a good idea to do regular cyclomatic complexity reviews (and, refactorings, based on the results) if any of these are true about your application:

  • It’s large (dozens of projects, hundreds/thousands of classes)
  • It’s being worked on by multiple developers
  • It will probably be used (and modified) for several years

You can also use cyclomatic complexity to determine how many automated tests you need for your program. If you want 100% coverage, you would need to create a unit test for each code path. The number of unit tests would be equal to the function’s cyclomatic complexity.

 

Disclaimer

I did not receive any compensation for mentioning NDepend. I pay for my copy with my own money. The link to their website is a non-affiliate link.

Leave a Comment