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:

  1. Readability: Code that is easy to read helps developers quickly grasp the logic and intent, facilitating better collaboration and code reviews.
  2. Defect Prevention: Lower complexity reduces the likelihood of bugs, as straightforward code paths are less prone to errors.
  3. 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:

  1. Code is considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one
  2. Code is considered more complex for each break in the linear flow of the code
  3. 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:

Ruby && conditional
1def destroy(post)
2 if current_user && current_user.admin?
3 post.destroy
4 end
5end

This uses &&, which contributes the method’s Complexity. If, however, you were to use the “safe navigation” operator and write:

Ruby safe navigation
1def destroy(post)
2 if current_user&.admin?
3 post.destroy
4 end
5end

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:

Simple total
1function count(a, b, c) {
2 var total = 0;
3
4 total += a;
5 total += b;
6 total += c;
7
8 return total;
9}

It flows from top to bottom with no breaks. Compare to this alternative implementation:

Looping total
1function count(a, b, c) {
2 var total = 0;
3 var nums = [a, b, c];
4
5 for (var i = 0; i < nums.length; i++) {
6 total += nums[i];
7 }
8
9 return total;
10}

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:

If statement
1if (env.debugMode()) {
2 System.out.println("Hello, world!");
3}

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:

Nested logic
1while(theWorldTurns) {
2 if(isMorning) {
3 try {
4 if (env.debugMode()) {
5 System.out.println("Hello, world!");
6 }
7 } catch (BadDay e) {
8 System.out.println("Yikes!");
9 }
10 }
11}

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:

Switch/case in PHP
1<?php
2switch ($meal) {
3 case "breakfast":
4 echo "Most important meal of the day! Enjoy.";
5 break;
6 case "lunch":
7 echo "A reasonably important meal of the day.";
8 break;
9 case "dinner":
10 echo "Alright, rounding out your day, very nice.";
11 break;
12 default:
13 echo "Snacking is important!";
14}

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.

See Also