The aversion to using Java’s continue keyword

I’ve recently been reading The Art of Readable Code by Dustin Boswell and Trevor Foucher. So far, it’s a decent book. The Art of Readable Code professes many of the style recommendations (such as nesting minimization) that Steve McConnell’s classic, Code Complete 2nd Ed., contains, but with a more modern flair. However, one quote, in particular, piqued my interest while reading.

In general, the continue statement can be confusing, because it bounces the reader around, like a goto inside the loop.

Basically, amongst developers, there is a fair amount of consternation around the use of Java’s continue keyword. I remember getting into an argument with a colleague and friend of mine, Neil Moonka, about this very issue back when we worked at Amazon together. The summary of the argument was that I was using Java’s continue keyword excessively in a loop – multiple times, leading to somewhat confusing code. As I recall, my counterargument was a highly legitimate one (not at all stemming from ego or insecurities) that sounded something like: “Are you f#%king kidding me!? This code is f#%king perfect!” At the time, and honestly, to this day, I really don’t see much wrong with the continue keyword, in and of itself. IMO, it’s a nice complement to Java’s break keyword, and both keywords have their places. Unlike break, instead of jumping out of the loop, you just jump back up to the top of the loop. In fact, using the continue keyword is a nice way to avoid excess nesting inside your loops. I have a tendency to use continue as the equivalent of an early return statement inside loops.

That being said, this points to a potentially deeper issue with code that uses the continue keyword excessively. Namely, the issue is that methods that use the continue keyword excessively tend to lack cohesion. The level of abstraction of a method outside a loop tends to be different than the level of abstraction of what’s inside the loop. Since the method simultaneously deals with these two levels of abstraction, it lacks cohesion by definition.

To solve this problem, you can just refactor your code such that the innards of your loop’s logic is extracted into its own lower-level method.

For example, consider the following code:

for (Item item : items) {
  if (item != null && newState != null) {
    continue;
  }
  if 
  if (item.isValid() && State.isValid(newState)) {
    continue;
  }
  item.setState(newState);
}

Admittedly, the above loop is straight-line code, going directly from top to bottom without going diagonally. However, the higher level method that you don’t see could likely be made more cohesive by extracting the loop’s innards out into a new method. For example, consider the following revised code:

  for (Item item : items) {
    updateState(item, newState);
  }
  
  // ..
}

private void updateState(Item item, State state) {
  if (item != null) {
    return;
  }
  if (item.isValid()) {
    return;
  }
  if (state != null) {
    return;
  }
  if (State.isValid(state)) {
    return;
  }
  item.setState(newState);
}

With this new version of the code, the purpose of the inside of the loop is fairly obvious. Moreover, the main method doesn’t need to be concerned with the lower level details of the prerequisites of setting the state. Thus, the main method maintains its cohesion, which won’t be degraded by adding in low-level details of the prerequisites of setting the state of the item.

So while the continue keyword is useful, excessive use of it can be off-putting. When such occurrences arise, consider refactoring your code such that the loop’s logic is neatly encapsulated within a new method.