Cyclomatic complexity: Why QAs (and Devs) should worry about it
Tags: testing, codequality
Published: 2021-10-17 22:43:19

This text will be the first of 3 and was inspired by a LinkedIn post, where a poll was made to know wether a developer should have a college degree. 90% voted no.

I agree with the results, but there are some contents you learn in college that can be useful at some point, and can make a difference in decision making. Three of these contents will be the subject of this series:

1. cyclomatic complexity;
2. asymptotic complexity;
3. binary math (focus on float numbers).

The complexities I studied in Data Structure course, and binary math in Fundamentals of Digital Computing and Introduction to Informatics (I didn't complete my degree in Computer Science).

In the texts, I'll give a straightforward and simplistic introduction to each subject, and how to use each to improve the quality of your application. At the end, I'll put links for those who want to go deeper.

## What is cyclomatic complexity?

Cyclomatic complexity, or conditional complexity, is the number of independent paths an algorithm can follow. It was developed by Thomas J. McCabe in 1976, published in the article “A Complexity Measure”. (Was nostalgic to see an Fortran example in the article. It was my first programming language and I used it practically all the 12 years I spent at university).

In practical terms, it is the amount of “ifs” and “elses” that a class, method, function or equivalent has.

According to McCabe, the maximum acceptable cyclomatic complexity is 10, but searching the internet, several sources indicate the maximum between 5 and 6.

## Cyclomatic complexity and tests

Cyclomatic complexity is the minimum number of tests that must be done. The greater the complexity, greater number of tests are needed, and greater the risk of bugs.

On the internet there are websites that calculate the cyclomatic complexity of a piece of code. Just copy and paste. I'll leave these 2 here:
* [JSHint](https://jshint.com/) to JavaScript
* [Lizard](http://www.lizard.ws/) for several languages like Java, Ruby, Python and Swift

Static analysis tools such as [SonarQube](https://docs.sonarqube.org/latest/user-guide/metric-definitions/#:~:text=It%20is%20the%20Cyclomatic%20Complexity,because%20keywords%20and%20functionalities%20do) or SonarLint, [ESLint](https://eslint.org/docs/rules/complexity), [Rubocop](https://docs.rubocop.org/rubocop/cops_metrics.html) also do this but you need to setup them in the project.

## In practice

On a team I belong, cyclomatic complexity was used as a reference for Planning Poker scores, because unit tests was required and the devs knew that the greater the complexity, the more tests they had to do, in addition to development.

On another team, I used it as an argument for not implementing a feature. The functionality was if the person passed the number in the address search bar, the app would take that number and automatically fill in the number in field. Otherwise, the field would be empty for manual filling.

When I said that this would increase complexity, I had to explain that it wasn't the complexity of development (be harder to implement) but of testing, and that would increase the risk of bugs. I explained two or three scenarios in which there could be an error and the PO agreed not to put it.

## Performance

Another point to worry about is performance. One developer I worked with always put the failure API return case in the first `if`, even if it happened only a few times. I always thought about how it will affect performance, even it's minimal. A time to send the request, more time to respond, more time to compare, more time to render… the person has already given up.

Searching the internet, I saw that this was a “good practice” for JavaScript, but only in some cases and just to make the code more readable. Unfortunately I didn't find anything that spoke of the performance. Not in JavaScript.

So, I found [Performance Consideration For C# Conditional Statements](https://www.c-sharpcorner.com/article/performance-consideration-for-c-sharp-conditional-statements/), as the title says, is for C#. In this article, Atul Sharma do some experiments with `ifs`, `if-else`, `switch-case`, with match cases in the first and last statement. The experiment shows that the decision can be more than 6 times slower if `true` only comes in the last if-else. Do the experiment in your language too!

## Good practices

I found several articles that teach how to substitute if-else and switch-case. I'll leave 3 here below, for Java, Ruby and JavaScript respectively.

* https://medium.com/swlh/5-ways-to-replace-if-else-statements-857c0ff19357
* https://medium.com/@qaschool/use-hashes-em-vez-de-case-statement-2316437470ea
* https://medium.com/better-programming/why-you-should-use-enumerations-in-javascript-3cbb0d03b8de

If you are QA, take a look at the code of the application you are testing. Suggest changes.

If you are a Dev, try to implement best practices in your code. Use Linters and static analysis tools. And read the [Clean Code](https://amzn.to/3mX2Q9w) book.

## To know more

* https://en.wikipedia.org/wiki/Cyclomatic_complexity
* http://literateprogramming.com/mccabe.pdf
* https://www.c-sharpcorner.com/article/performance-consideration-for-c-sharp-conditional-statements/

## Appendix: Real production code with complexity 12

And another 50 warnings, measured in [JSHint](https://jshint.com/). Some words were removed and/or modified to not identify the product and translated to English.

```javascript
function checkNavigation() {
    _event(
      'new_services',
      'button',
      isEmpty(title) === false ? title.toLowerCase() : subtitle.toLowerCase(),
    )
    if (typePos) {
      if (!hasTicket) {
        if (payment) {
          Alert.alert('OK', "You don't have any open invoice!")
        } else if (alertOpen) {
          Alert.alert('OK', "You don't have any open invoice!")
        } else {
          if (isEmpty(propsNavigate) === true) {
            navigation.navigate(navigateTo)
          } else {
            console.log('propsNavigate--> 1', propsNavigate)
            navigation.navigate(navigateTo, propsNavigate)
          }
        }
      } else if (alertOpen) {
        openAlert()
      } else if (hasTicket) {
        if (isEmpty(propsNavigate) === true) {
          navigation.navigate(navigateTo)
        } else {
          console.log('propsNavigate--> 2', propsNavigate)
          navigation.navigate(navigateTo, propsNavigate)
        }
      }
    } else if (type3) {
      if (Store.getState().extractPreReducer.balance === '0') {
        Alert.alert(
          "You don't have any open debts!",
          [
            {
              text: 'Close',
            },
            {
              text: 'Further informations',
              onPress: () => {
                navigation.navigate('HowWorkScreen')
              },
            },
          ],
        )
      }
    } else {
      navigation.navigate(navigateTo)
    }
  }
```