Complexity
Complexity (or cognitive complexity) is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, our Complexity metric tells you how difficult your code will be to read and understand.
Understanding and managing code complexity is crucial for several reasons:
- Readability: Code that is easy to read helps developers quickly grasp the logic and intent, facilitating better collaboration and code reviews.
- Defect Prevention: Lower complexity reduces the likelihood of bugs, as straightforward code paths are less prone to errors.
- Efficiency: Developers can work more efficiently with less complex code, leading to faster development cycles and more reliable software.
By identifying and addressing complex code, developers can enhance the maintainability, readability, and overall quality of their codebase.
Qlty can help you identify which files and functions are overly difficult to understand and prevent introducing them into your code.
Complexity insights are provided by Qlty CLI and Qlty Cloud.
Our Complexity metric powers our Code Smells for high function complexity and high file complexity.
The Idea
A functions’s Complexity is based on a few simple principles:
- Code is considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one
- Code is considered more complex for each break in the linear flow of the code
- Code is considered more complex when flow breaking structures are nested
Let’s break those down:
Shorthand
Let’s say you’re writing Ruby, and you write:
This uses &&
, which contributes the method’s Complexity. If, however, you were to use the “safe navigation” operator and write:
This is a bit more intuitive, and doesn’t contribute to the method’s Complexity.
Breaks in flow
When a method’s logic flows from top to bottom, it is very easy to understand. Take this JavaScript snippet for example:
It flows from top to bottom with no breaks. Compare to this alternative implementation:
The for
loop changes the function so it no longer flows directly from top to bottom, but now loops in circles a few times in the middle, which contributes to Complexity for the reader. It’s a small thing, but it adds up.
Other examples of breaks in flow:
- loops
- conditionals
- catching/rescuing exceptions
- switch or case statements
- sequences of logical operators (e.g.
a || b && c || d
) - recursion
- jumps to labels
Nesting
The more deeply-nested your code gets, the harder it can be to reason about. This line of Java is pretty straight-forward:
But when you’re nested within multiple layers of conditionals or loops, a simple conditional isn’t just a simple conditional, and leads to a greater Complexity.
Here’s the same conditional, in a totally different context:
In this example, the same conditional adds more complexity. Context matters.
The following count as nesting:
- conditionals
- loops
- try/catch blocks
Difference from Cyclomatic Complexity
As an example of code which is easy to understand, but difficult to test, consider this PHP example:
This code is perfectly intuitive to understand, but if you wanted to test it exhaustively, you would need to write at least four test cases. This is what we mean when we say its Cyclomatic Complexity is higher than its (cognitive) Complexity.
Further reading
Please see the Cognitive Complexity: A new way of measuring understandability white paper by G. Ann Campbell of SonarSource for further detail and many examples.