Cyclomatic Complexity: A Complete Guide
Imagine you're driving through a complex intersection with multiple traffic lights and lanes. Each traffic light represents a decision point where you must choose a direction to continue your journey. The more traffic lights you encounter, the more complex the intersection becomes.
Just as navigating through a complex intersection requires careful attention to traffic signals and lane changes, understanding cyclomatic complexity in code involves analyzing the number of decision points and possible paths through your code. The greater the cyclomatic complexity, the more intricate and potentially error-prone the code may be.
Quality is often subjective. However, software quality isn’t. There are metrics that software developers use to define what quality code looks like, and one of the major ones is code complexity. One metric that helps developers assess code complexity is cyclomatic complexity.
What is Cyclomatic Complexity?
Code Cyclomatic Complexity is a quantitative measure of the complexity of a program. This metric measures linearly independent paths through a program's source code. In simpler terms, it quantifies the number of decision points in the code.
High Cyclometric Complexity can increase the risk of errors in code. The higher your code’s cyclomatic complexity is, the more difficult it is to navigate. The measurement is derived from analyzing the program's control flow graph, which serves as a visual representation aiding developers in comprehending the program's execution flow.
Like a map guiding one through a journey, the control flow graph illustrates the sequence of actions within the program, depicting loops, conditions, and other control structures.
The control flow graph functions like a diagram outlining the sequential execution of these instructions, with nodes representing individual steps and arrows delineating the order in which they are carried out.
Cyclomatic Complexity is measured using the following formula:
M = E - N + 2P
In this formula, M stands for Cyclomatic Complexity, E refers to the number of edges in the control flow graph, N refers to the number of nodes in the control flow graph, and P refers to the number of connected components.
Today, calculating Cyclomatic Complexity is pretty easy. Automated tools are available (like Codacy) that you can plug into your Integrated Development Environments (IDEs) to calculate cyclomatic complexity automatically.
What Does Cyclomatic Complexity Look Like In Code?
Consider the following code snippet and Cyclomatic Complexity examples:
if score >= 90:
grade = 'A'
elif score >= 80:
grade = 'B'
elif score >= 70:
grade = 'C'
elif score >= 60:
grade = 'D'
grade = 'F'
In this Python function calculate_grade, the input parameter score represents a numerical score, and the function returns a corresponding letter grade based on certain score thresholds.
Now, let's examine the cyclomatic complexity of this function.
This code snippet has five decision points, each corresponding to an if or elif statement. These decision points are where the code branches based on the value of score.
Each decision point creates multiple paths through the code, depending on the possible combinations of conditions. For example:
If score is greater than or equal to 90, the path leads to an A grade.
If score is between 80 and 89, the path leads to a B grade.
If score is between 70 and 79, the path leads to a C grade.
If score is between 60 and 69, the path leads to a D grade.
If score is less than 60, the path leads to an F grade.
Using the cyclomatic complexity formula, we can calculate the cyclomatic complexity of this function.
In this case, E = 6, N = 7, and P = 1.
Substituting these values into the formula: M = 6 - 7 + 2(1) = 1.
A cyclomatic complexity of 1 indicates a single linear path through the code. This corresponds to the “normal” flow of execution without any branching or loops.
While the cyclomatic complexity of this function is low, it's still possible to refactor the code to make it even simpler. For example, using a dictionary or lookup table instead of multiple if statements could reduce complexity and improve readability.
How is Cyclomatic Complexity Used?
Cyclomatic Complexity serves as a helpful metric for software developers, aiding them in performing the following tasks:
- Identifying Error-Prone Code: By pinpointing complex areas within the code base, developers identify regions more likely to contain errors. This proactive approach allows teams to focus their testing and debugging efforts where needed most, potentially reducing the number of bugs.
- Assessing Maintainability and Readability: High Cyclomatic Complexity values often correlate with difficult-to-understand and maintain code. By assessing the complexity of different sections of code, developers can gauge the overall maintainability and readability of the software, enabling teams to prioritize areas for improvement, such as rewriting convoluted logic or adding comments to clarify complex sections.
- Guiding Refactoring Efforts: Cyclomatic Complexity measurements can guide code refactoring efforts. Developers can use these software quality metrics to identify specific functions or methods that would benefit from refactoring to simplify their logic. By breaking down complex code into smaller, more manageable pieces, teams can improve code maintainability and reduce the likelihood of introducing errors during future modifications.
- Estimating Testing Effort: The complexity of a program directly impacts the effort required to test it thoroughly. A larger number of potential execution paths translates to increased testing complexity. With this metric, teams can estimate the testing effort required to achieve comprehensive code coverage.
Are There Any Disadvantages to Measuring Cyclomatic Complexity?
While Cyclomatic Complexity is a very useful metric, it’s certainly not the only one teams should be using, simply because it may not capture all aspects of code complexity. Cyclomatic Complexity focuses primarily on control flow structures, such as loops and conditional statements, potentially overlooking other aspects of code complexity, such as data dependencies or architectural issues.
As a result, being guided only by this metric can lead to overly simplistic solutions. Developers may be tempted to reduce Cyclomatic Complexity by introducing overly simplistic solutions that sacrifice readability or maintainability. While reducing complexity is desirable, it should not come at the expense of other important code quality attributes.
How to Reduce Cyclomatic Complexity
When your development team is able to measure the Cyclomatic Complexity of your code, it can now create and implement strategies to help reduce it:
Write Small Functions
Smaller functions are sometimes easier to read and understand, even without in-line comments. If your function has fewer lines of code, it will also have lower complexity. You should identify the core responsibilities of each function and extract everything else to its functions. The rule should be one function, one job. This will also help with code reusability.
Delete Duplicate Code
Duplicate code refers to pieces of code that do almost or exactly the same thing. Having duplicate code increases cyclomatic complexity and compromises your code’s readability and efficiency. There are several ways to go about duplicate code, so choose the one that is better for your projects:
- Extract the common parts of your code into dedicated functions.
- Extract generic functions into packages that your entire organization can reuse.
- Use design patterns and templates that allow you to reuse code.
Remove Dead Code
Dead code refers to pieces of code that are never executed at run-time or that are executed but whose result is never used and does not affect the rest of the codebase. Dead code wastes computation time and memory. When you delete dead code, you can lower your Cyclomatic Complexity and increase your code coverage.
Reduce the Number of Decision Structures
Decision structures, like if and switch statements can cause an incredibly high number of branches in your codebase, increasing cyclomatic complexity. This is where code patterns can help you out.
For example, you can use the decorator pattern instead of flag arguments with an if-statement. It’s a structural design pattern used in object-oriented programming. It allows behavior to be added to individual objects, statically or dynamically, without affecting the behavior of other objects from the same class.
For switch statements, you can use the strategy pattern. This design pattern allows you to define a family of algorithms, put them into a separate class, and make their objects interchangeable.
However, there are still cases where using if and switch statements is the best approach. But, when possible, use strategies that will allow you to lower your cyclomatic complexity.
Avoid Flag Arguments in Functions
Flag arguments are boolean parameters that you can use when changing how a function works while preserving the old behavior. However, they cause the cyclomatic complexity of your code to increase.
You can use other strategies to achieve the same result, such as refactoring the function by creating a new one, maintaining the original function, and extracting the common parts into a private function. You can use the decorator pattern here as well.
Use Control Structures Wisely
Minimize using nested control structures such as nested loops and conditionals. Complex nesting increases Cyclomatic Complexity and makes code harder to understand and debug. Refactor nested structures into simpler, sequential logic or extract them into separate functions where possible.
Write Comprehensive Unit Tests
Unit tests ensure that the code behaves as expected and serve as a safety net when refactoring complex areas. By identifying and testing different code paths, you can be sure that your changes are correct and pinpoint areas for further refactoring.
Automate Code Reviews to Decrease Cyclomatic Complexity
Cyclomatic complexity is among the most valuable metrics in software engineering productivity. It has important implications for your code quality, maintenance, and testing. As such, keeping your cyclomatic complexity under control will help you achieve a healthy codebase.
To keep this metric in check, performing automated code reviews and analysis with a tool like Codacy is a great starting point. Start your 14-day free trial today to see how it works.