E11010: excessive complexity - refactor

Category: E11xxx Code quality and IR analysis · Reported in phase: PRE_IR_CHECKS · Classification: EXCESSIVE_COMPLEXITY · All error codes

What went wrong

Your function/method exceeds the cyclomatic complexity threshold of 45. Cyclomatic complexity measures independent paths through code. Each control flow statement (if/while/for/switch/try) adds a path.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 1020 (My function is too complex and the compiler rejects it...)
  • ek9 -q 1020 (My function is too complex and the compiler rejects it...)
  • ek9 -q 1020 (My function is too complex and the compiler rejects it...)

How to fix it

  1. Extract helper methods for each logical branch
  2. Use guards to reduce nesting
  3. Replace nested if/else with switch when appropriate
  4. Break up complex coalescing expressions into intermediate variables

Expression complexity

  • Coalescing operators (?:, <?, >?) add complexity
  • Extract complex coalescing expressions to separate functions

Common causes

  • Too many control flow statements
  • Complex expressions with coalescing operators
  • Multiple exit points and error handling paths

See also

  • E11010: cyclomatic complexity (this error)
  • E11021: cognitive complexity

https://ek9.io/quality.html#complexity https://ek9.io/forAI.json#$.qualityMetrics.metrics[?(@.errorCode=="E11010")]

Examples

Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.

Fails with E11010: excessive complexity - refactor

#!ek9
defines module error.e11010.bad

  defines function

    isPositive() as pure
      -> item as Integer
      <- rtn as Boolean: item > 0

    isLarge() as pure
      -> item as Integer
      <- rtn as Boolean?
      threshold <- 10
      rtn: item > threshold

    doubleIt() as pure
      -> item as Integer
      <- rtn as Integer: item * 2

    addTen() as pure
      -> item as Integer
      <- rtn as Integer: item + 10

    joiner() as pure
      ->
        a as Integer
        b as Integer
      <- rtn as Integer: a + b

  defines function

    <?-
      Multiple complex pipelines with type-changing operations push the
      function complexity over the EXCESSIVE_COMPLEXITY threshold of 45.

      Pipeline 1: sort(1) + group(2) + flatten(2) + map(2) + tee(2) + filter(1) + head(1) = 11
      Pipeline 2: filter(1) + map(2) + sort(1) + map(2) + skip(1) + head(1) = 8
      Pipeline 3: sort(1) + filter(1) + map(2) + map(2) + tee(2) + uniq(1) = 9
      Pipeline 4: filter(1) + sort(1) + join(2) = 4
      Sources: 4 x cat(1) = 4, terminals: 4 x (1) = 4
      Other: exit(1) + require(3) + if(2) + comparisons(4) = 10
      Total: 11 + 8 + 9 + 4 + 4 + 4 + 10 = 50
    -?>
    BadStreamComplexity()
      stdout <- Stdout()

      sideCapture1 <- List() of Integer
      sideCapture2 <- List() of Integer

      //Pipeline 1: sort, group, flatten, map, tee, filter, head (7 stages, 11 weighted)
      cat [8, 3, 5, 3, 8, 1, 5, 2, 1, 8]
        | sort
        | group
        | flatten
        | map by doubleIt
        | tee in sideCapture1
        | filter by isPositive
        | head 3
        > stdout

      //Pipeline 2: filter, map, sort, map, skip, head (6 stages, 8 weighted)
      cat [100, -50, 200, -75, 300, 25, -10, 150]
        | filter by isPositive
        | map by doubleIt
        | sort
        | map by addTen
        | skip 1
        | head 3
        > stdout

      //Pipeline 3: sort, filter, map, map, tee, uniq (6 stages, 9 weighted)
      cat [5, 10, 15, 5, 20, 10, 25, 15, 30]
        | sort
        | filter by isLarge
        | map by doubleIt
        | map by addTen
        | tee in sideCapture2
        | uniq
        > stdout

      //Pipeline 4: filter, sort, join (3 stages, 4 weighted)
      total <- cat [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
        | filter by isPositive
        | sort
        | join with joiner
        | collect as Integer

      //Pipeline 5: sort, group, flatten, map, filter, head (6 stages, 10 weighted)
      cat [7, 2, 9, 2, 7, 4, 9, 1]
        | sort
        | group
        | flatten
        | map by addTen
        | filter by isLarge
        | head 5
        > stdout

      //Pipeline 6: filter, map, sort, map, uniq (5 stages, 7 weighted)
      cat [11, 22, 33, 11, 44, 22, 55]
        | filter by isPositive
        | map by doubleIt
        | sort
        | map by addTen
        | uniq
        > stdout

      //Pipeline 7: sort, group, flatten, map, tee, filter, map, uniq, head (9 stages, 16 weighted)
      cat [3, 6, 9, 3, 12, 6, 15, 9, 18]
        | sort
        | group
        | flatten
        | map by doubleIt
        | tee in sideCapture1
        | filter by isPositive
        | map by addTen
        | uniq
        | head 5
        > stdout

      require total?
      require length sideCapture1 > 0
      require length sideCapture2 > 0

      if total > 0
        stdout.println($total)
      minCapture <- 2
      if length sideCapture1 > minCapture
        stdout.println("captured enough")

Fixed: Give each pipeline its own function

#!ek9
defines module error.e11010.good

  defines function

    isPositive() as pure
      -> item as Integer
      <- rtn as Boolean: item > 0

    doubleIt() as pure
      -> item as Integer
      <- rtn as Integer: item * 2

    addTen() as pure
      -> item as Integer
      <- rtn as Integer: item + 10

    firstThreeDoubled()
      -> numbers as List of Integer
      <- rtn as List of Integer: List()
      rtn: cat numbers | sort | uniq | map by doubleIt | filter by isPositive | head 3 | collect as List of Integer

    shiftedPositives()
      -> numbers as List of Integer
      <- rtn as List of Integer: List()
      rtn: cat numbers | filter by isPositive | map by addTen | sort | collect as List of Integer

  defines program

    StreamsInParts()
      stdout <- Stdout()
      stdout.println($firstThreeDoubled([8, 3, 5, 3, 8, 1]))
      stdout.println($shiftedPositives([100, -50, 200]))