Inappropriate coding practices - Cyclomatic complexity - Swift

Inappropriate coding practices - Cyclomatic complexity - Swift

Need

Improvement of coding practices to reduce cyclomatic complexity

Context

  • Usage of Swift 5.0 for iOS app development

Description

Non compliant code

        func complexFunction(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int, g: Int, h: Int, i: Int, j: Int) -> Int {
    var result = 0
    if a > b {
        result += a
    } else {
        result += b
    }
    if c > d {
        result += c
    } else {
        result += d
    }
    if e > f {
        result += e
    } else {
        result += f
    }
    if g > h {
        result += g
    } else {
        result += h
    }
    if i > j {
        result += i
    } else {
        result += j
    }
    return result
}
        
        

The above Swift function complexFunction is an example of a function with high cyclomatic complexity. Cyclomatic complexity is a software metric used to indicate the complexity of a program. It is a quantitative measure of the number of linearly independent paths through a program's source code.

In the context of the complexFunction, it has a cyclomatic complexity of 11 (1 for the function itself + 2 for each of the 5 if-else conditions). This is considered high as it is generally recommended to keep the cyclomatic complexity below 10.

The high complexity in this function comes from the numerous decision points
- the if-else conditions. Each of these conditions adds a new path through the program, increasing the number of test cases needed to achieve full code coverage.

This complexity makes the function harder to understand, maintain, and test, which can lead to errors and potential security vulnerabilities. It can also make it harder to detect and fix existing vulnerabilities.

To reduce the cyclomatic complexity, the code could be refactored to split the functionality into smaller, simpler functions. Additionally, the repeated if-else conditions suggest that the code could benefit from a more general approach, such as a loop or a more sophisticated data structure.

Steps

  • Reduce the number of conditional statements in the function.
  • Refactor the code to use loops or data structures instead of multiple if-else statements.
  • Break down the complex function into smaller, more manageable functions.
  • Apply the Single Responsibility Principle to ensure each function has a single purpose.
  • Use meaningful variable names and comments to improve code readability.
  • Consider using design patterns or architectural patterns to simplify the code structure.
  • Perform code reviews and seek feedback from other developers to identify areas for improvement.
  • Consider using automated code analysis tools to detect and highlight areas of high cyclomatic complexity.
  • Refactor the code to reduce duplication and improve code reuse.
  • Consider using functional programming techniques to simplify the code logic.

Compliant code

        func maxOfTwoNumbers(_ num1: Int, _ num2: Int) -> Int {
    return num1 > num2 ? num1 : num2
}

func complexFunction(_ numbers: [Int]) -> Int {
    var result = 0
    for i in stride(from: 0, to: numbers.count, by: 2) {
        result += maxOfTwoNumbers(numbers[i], numbers[i+1])
    }
    return result
}
        
        

The refactored code reduces the cyclomatic complexity by breaking down the complex function into smaller, more manageable functions and using loops instead of multiple if-else statements.

The function maxOfTwoNumbers is a helper function that takes two integers as input and returns the maximum of the two. This function is used to replace the multiple if-else statements in the original function.

The complexFunction now takes an array of integers as input. It iterates over the array in steps of two, using the stride function. For each pair of numbers, it calls the maxOfTwoNumbers function and adds the result to the result variable.

This refactoring significantly reduces the cyclomatic complexity of the code, making it easier to understand, maintain, and test. It also improves code reuse by separating the logic for finding the maximum of two numbers into a separate function that can be used elsewhere in the code if needed.

The code now adheres to the Single Responsibility Principle, as each function has a single, clear purpose. The use of meaningful function and variable names, as well as the removal of unnecessary conditional statements, improves code readability.

Remember to perform code reviews and use automated code analysis tools to ensure the cyclomatic complexity remains low as the codebase evolves. Consider using functional programming techniques and design patterns to further simplify the code structure and logic.

References