The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Perl::Critic::Policy::CognitiveComplexity::ProhibitExcessCognitiveComplexity - Avoid code that is nested, and thus difficult to grasp.

DESCRIPTION

Cyclomatic Complexity was initially formulated as a measurement of the "testability and maintainability" of the control flow of a module. While it excels at measuring the former, its underlying mathematical model is unsatisfactory at producing a value that measures the latter. A white paper from SonarSource* describes a new metric that breaks from the use of mathematical models to evaluate code in order to remedy Cyclomatic Complexity's shortcomings and produce a measurement that more accurately reflects the relative difficulty of understanding, and therefore of maintaining methods, classes, and applications.

* https://blog.sonarsource.com/cognitive-complexity-because-testability-understandability/

Basic criteria and methodology

A Cognitive Complexity score is assessed according to three basic rules:

1. Ignore structures that allow multiple statements to be readably shorthanded into one 2. Increment (add one) for each break in the linear flow of the code 3. Increment when flow-breaking structures are nested

Additionally, a complexity score is made up of three different types of increments:

A. Nesting - assessed for nesting control flow structures inside each other B. Structural - assessed on control flow structures that are subject to a nesting increment C. Fundamental - assessed on statements not subject to a nesting increment

While the type of an increment makes no difference in the math - each increment adds one to the final score - making a distinction among the categories of features being counted makes it easier to understand where nesting increments do and do not apply.

EXAMPLES

Some examples from the whitepaper, translated to perl.

  #                                Cyclomatic Complexity    Cognitive Complexity

Most simple case: subs themselves do not increment the cognitive complexity.

  sub a {                          # +1
  }                                # =1                      =0

given/when increments cognitive complexity only once.

  sub getWords {                   # +1
      my ($number) = @_;
      given ($number) {            #                         +1
        when (1)                   # +1
          { return "one"; }
        when (2)                   # +1
          { return "a couple"; }
        default                    # +1
          { return "lots"; }
      }
}                                  # =4                      =1

The deeper the nesting, the more control-structures add to the complexity.

goto, next and last break the linear flow, which increments the complexity by one.

  sub sumOfPrimes {
    my ($max) = @_;
    my $total = 0;
    OUT: for (my $i = 1; $i <= $max; ++$i) { #               +1
        for (my $j = 2; $j < $i; ++$j) { #                   +2
            if ($i % $j == 0) { #                            +3
                 next OUT; #                                 +1
            }
        }
        $total += $i;
    }
    return $total;
  } #                                                        =7

Anonymous functions do not increment the complexity, but the nesting.

  sub closure {
      sub { #                                                +0 (nesting=1)
          if(1) { #                                          +2 (nesting=1)
              return;                                        +0 (nesting=2)
          }
      }->();
  }                                                          =2

Cognitive Complexity does not increment for each logical operator. Instead, it assesses a fundamental increment for each sequence of logical operators.

  sub boolMethod2 {
      if( #                                                  +1
      $a && $b && $c #                                       +1
      || #                                                   +1
      $d && $e) #                                            +1
      {
  } #                                                        =4