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