All Loops Are a Code Smell
By: Randal Kamradt Sr
Tags: Java8, Code Smells, Java, Functional Programming

All Loops Are a Code Smell

Image by Christopher Kuszajewski from Pixabay

Loops are a fundamental part of programming. We need to do something for each item in a list. We need to read input until input is exhausted. We need to put n number of boxes on the screen. But every time I see a loop being added to code in a PR, my eyebrows go up. Now I have to examine the code closely to ensure the loop will always terminate. Sometimes it’s very easy to tell, but I’d just as soon not have to make that determination. I want to see all loops disappear into some well-tested library. But I still see them creeping in, so I thought I’d show just how to eliminate them in cases that might tempt you to use them.

The key to making loops disappear is functional programming. All you should supply is the code to execute in the loop and the parameters of the loop (what it should loop on). I’ll be using Java as an example language, but a lot of languages support this style of functional programming which can help to eliminate loops in your code.

The simplest case is doing something for each element in a list.

List<Integer> list = List.of(1, 2, 3);

// bare for loop.

for(int i : list) {

System.out.println("int = " + i);

} // controlled for each

list.forEach(i -> System.out.println("int = " + i));

In this simplest case, there’s not a lot of benefit either way. But the second way gets us into the habit of not using the bare for loop and, to my eye, has a slightly cleaner syntax.

The forEach for me is also problematic and should only be used for methods that contain safe side-effects. By safe side-effects, I mean they don’t alter program state. In the above example, we are just logging, so it’s fine to use. Other examples of safe side-effects are writing to a file, database, or message queue.

Unsafe side-effects alter program state. Here’s an example, and how to fix it:

// bad side-effect, the loop alters sum

int sum = 0;

for(int i : list) {

sum += i;

}

System.out.println("sum = " + sum); // no side-effect, sum is calculated by loop

sum = list

.stream()

.mapToInt(i -> i)

.sum();

System.out.println("sum = " + sum);

Another example I see all too often:

// bad side-effect, the loop alters list2

List<Integer> list2 = new ArrayList<>();

for(int i : list) {

list2.add(i);

}

list2.forEach(i -> System.out.println("int = " + i)); // no side effect, the second list is built by the loop

list2 = list

.stream()

.collect(Collectors.toList());

list2.forEach(i -> System.out.println("int = " + i));

One problem that occurs is when you need the index within the method that processes list items, but that can be solved as well:

// bare for loop with index:

for(int i = 0; i < list.size(); i++) {

System.out.println("item at index "

+ i

+ " = "

+ list.get(i));

} // controlled loop with index:

IntStream.range(0, list.size())

.forEach(i -> System.out.println("item at index "

+ i

+ " = "

+ list.get(i)));

How about the age-old problem of reading each line in a file until the file is exhausted.

BufferedReader reader = new BufferedReader(

new InputStreamReader(

LoopElimination.class.getResourceAsStream("/testfile.txt")));

// while loop with clumsy looking syntax

String line;

while((line = reader.readLine()) != null) {

System.out.println(line);

} reader = new BufferedReader(

new InputStreamReader(

LoopElimination.class.getResourceAsStream("/testfile.txt")));

// less clumsy syntax

reader.lines()

.forEach(l -> System.out.println(l));

In the above case, we had a very convenient lines method that returned a Stream type.

But what if you’re reading character-by-character? There’s no method of the InputStream class that returns a Stream<Character> . We’ll have to make our own Stream :

InputStream is =

LoopElimination.class.getResourceAsStream("/testfile.txt");

// while loop with clumsy looking syntax

int c;

while((c = is.read()) != -1) {

System.out.print((char)c);

}

// But this is even uglier

InputStream nis =

LoopElimination.class.getResourceAsStream("/testfile.txt");

// Exception handling makes functional programming ugly

Stream.generate(() -> {

try {

return nis.read();

} catch (IOException ex) {

throw new RuntimeException("Error reading from file", ex);

}

})

.takeWhile(ch -> ch != -1)

.forEach(ch -> System.out.print((char)(int)ch));

This is one instance where the while loop looks better. Also the Stream version uses the generate function which returns an infinite stream of items, so I have to inspect further to make sure the generation terminates, which it does because of the takeWhile method. The InputStream class is problematic because it doesn’t have a peek method which we would need to use to create an Iterator that could easily be turned into a Stream . It also throws a checked exception which really uglifies functional programming. I might give a PR a pass with the while statement in this case.

To make the above problem cleaner, you could create a new type IterableInputStream like this:

static class InputStreamIterable implements Iterable<Character> {

private final InputStream is;

public InputStreamIterable(InputStream is) {

this.is = is;

}

public Iterator<Character> iterator() {

return new Iterator<Character>() {

public boolean hasNext() {

try {

// poor man's peek:

is.mark(1);

boolean ret = is.read() != -1;

is.reset();

return ret;

} catch (IOException ex) {

throw new RuntimeException(

"Error reading input stream", ex);

}

}

public Character next() {

try {

return (char)is.read();

} catch (IOException ex) {

throw new RuntimeException(

"Error reading input stream", ex);

}

}

};

}

}

Then the looping problem is greatly simplified:

// use a predefined inputstream iterator:

InputStreamIterable it = new InputStreamIterable(

LoopElimination.class.getResourceAsStream("/testfile.txt"));

StreamSupport.stream(it.spliterator(), false)

.forEach(ch -> System.out.print(ch));

If you encounter this type of while loop a lot, you might invest in creating and using a specialized Iterable class. But if it’s a one-off, it’s probably not worth it, and just an example of old Java being incompatible with new Java.

So the next time you’re writing for or while in your code, stop and think for a moment how that could be better accomplished with a forEach or a Stream .

The code for this article can be found in my GitHub repository