Functionally Identical Code, Different Branch Count (Java)

See the below functionally equivalent implementations, resulting in different branch count by IntelliJ IDEA.

// (1) Classic for loop
// --------------------------------
// 6 branches (condition outcomes): 
    // 2 for the loop        (exit loop? -> true/false)
    // 4 for the if decision (even num?  -> true/false; large num?  -> true/false)
 List<Integer> getLargeEvenNums(List<Integer> list) {
      List<Integer> result = new ArrayList<>();

        for (int i = 0; i < list.size(); i++) {
            var num = list.get(i);
            if (num % 2 == 0 && num > 10) {
                result.add(num);
            }
        }
        return result;
    }

// (2) Enhanced for loop
// --------------------------------
// 6 branches, same as above
// this for-each loop is just syntactic sugar over the classic for loop

//...
for (Integer num : list) {
    if (num % 2 == 0 && num > 10) {
        result.add(num);
    }
}
//...


// (3) Stream API - one big predicate
// --------------------------------
// 4 branches 
    // loop is now abstracted away into the Stream API
    // coverage tool cannot "see" into the Java standard library or JAR files
    // so only the 4 branches counted for (A && B)
    // 3 values needed to reach 100% coverage
var evenAndLarge = n -> (n % 2 == 0) && (n > 10);

List<Integer> getLargeEvenNums(List<Integer> list) {
     return list.stream()
                .filter(evenAndLarge)
                .toList();
    }


// (4) Stream API - two predicates chained with .and()
// --------------------------------
// 2 branches only
    // loop abstracted away
    // && replaced by .and(), thus this branch is also moved into the Java library
    // coverage tool cannot see neither the loop branches, nor the .and()
    // only one predicate "even" can be counted
    // 2 values needed to reach 100% coverage    
var even = n -> n % 2 == 0;
var large = n -> n > 10;

List<Integer> getLargeEvenNums(List<Integer> list) {
        return list.stream()
                .filter(even.and(large))
                .toList();
    }

"Losing" 2 branches from the loop is OK if the intention is to just iterate over the entire collection.

But "losing" one or more conditions through .and() is most likely an unintended side-effect, leading developers to have false confidence in their coverage.

Last updated