What Is Cyclomatic Complexity?
Cyclomatic complexity basically measures how much your code branches. Every time there’s an if statement or other control block like a loop, cyclomatic complexity goes up, since the graph will look more and more like a tree.
If you imagine your code as a series of actions (functions, method calls, variable assignments) connected via control flow, you’ll get an abstract graph that you can use to better understand the complexity. For common control flows like if statements and for loops, the graphs look like this:
The formula for it is simple; take the number of edges in the graph (the arrows connecting everything) and subtract the number of nodes in the graph (the actions themselves).
For example, this code has a cyclomatic complexity of one, since there aren’t any branches, and it just calls WriteLine over and over. Since it’s just perfectly linear code, the number of nodes will cancel out the number of edges, giving a cyclomatic complexity of one.
For more complicated code with branches, the complexity will be higher. This code, which contains a switch statement, has a complexity of 6, because there are many different paths the code can take. Each case in the switch statement adds complexity, as it can lead to different outputs with differing inputs.
Cyclomatic complexity is only calculated within the scope of the function. If a function calls another function that has a high cyclomatic complexity, it’s only counted as a single node, and doesn’t add anything to the caller, despite technically adding complexity to the program in a general sense.
Is Cyclomatic Complexity Useful?
Cyclomatic complexity isn’t a perfect metric. It’s a very basic metric and looks over the nuance of the code itself. Of course, you can still have terrible code with low complexity, or decent code with high complexity. But, in general, it’s still quite useful for getting a roundabout sense of how complex a program is.
For the most part, complexity under 6 to 8 is probably fine as long as the code itself is well formatted. Anything from 8-15 is questionable, and anything over 15 is probably not great. Anything over 25 is almost certainly a problem unless proven otherwise.
While having a high cyclomatic complexity in any given function isn’t the end of the world, it can be indicative of a larger problem. High complexity functions are harder to maintain and prone to more bugs, since there are more things to go wrong. And higher complexity functions directly lead to higher complexity unit tests, which can make the code hard to maintain in the long run due to the difficulty of testing.
Visual Studio and other IDEs will calculate aggregate complexities of entire classes and namespaces, which can be useful for tracking down your most complex classes. You can sort by highest complexity and drill down into individual functions.
Often, code review can take cyclomatic complexity into account, even flagging problematic functions that may need manual review. This can make it a very useful tool for maintaining a clean and orderly codebase.
Looking For Bad Code
Many IDEs, like Visual Studio, will have built in tools for calculating cyclomatic complexity and other code metrics for your entire codebase.
To do so from Visual Studio, click Analyze > Calculate Code Metrics > For Solution.
This will bring up the “Code Metrics” panel that will show a breakdown for your solution. You can sort by complexity in descending order to view the most problematic namespaces.
Alongside complexity, Visual Studio also presents a “Maintainability Index” which scores the method from 0-100 on high easily it can be maintained, as well as “Class Coupling,” which lists how many classes are referenced from that function or class.
Here, Visual Studio pointed out a 400 line method of mine that scored a whopping 72 on the complexity scale as well as a 14/100 on the maintainability index (presented as a yellow triangle danger sign), and references 65 different classes.
You may be sent directly into the five stages of grief at the result. “But, this is a really long coroutine that has a bunch of tasks to do!” I tell myself, while trying to deny that the code I wrote is mathematically bad, to the point where Visual Studio throws a warning. A complexity of 72 is certainly in need of cleaning up.
In this case, the fix was simple — the coroutine has a bunch of tasks to do, so I break those tasks up into smaller coroutines, and replace the main method with calls to subroutines. The overall code didn’t change, and neither did the total complexity of the class itself, but now the main function isn’t a 400 line monstrosity.