Avoid continue

“I have never seen a piece of code that was not improved by refactoring it to remove the continue statement.” —Douglas Crockford, JavaScript: The Good Parts (page 111)

Don’t use the continue keyword. It’s effectively a goto and it breaks up the flow of code in a similar way. It’s too easy, when skimming code, to miss it:

for (Node node : nodeList) {
    if (node.isBad()) {
        continue;
    }

    processNode();
}

It’s also more logically difficult to parse. The reader has to think, “If it’s bad, then we continue, otherwise we process.” Easier to instead think, “If it’s not bad, we process,” like this:

for (Node node : nodeList) {
    if (!node.isBad()) {
        processNode();
    }
}

If the content of your loop is too large or logically complex to effectively do this, then it’s too large to put in-line and should be broken out into a different function, where you can use return. For example:

for (Node node : nodeList) {
    if (node.isQuestionable()) {
        NodeData nodeData = node.getNodeData();
        if (nodeData.isBad()) {
            continue;
        }
    }

    processNode();
}

Here the continue is even more hidden than in the first example, but it’s harder to simply invert the condition. You can refactor in two ways. The first uses return:

for (Node node : nodeList) {
    potentiallyProcessNode(node);
}

void potentiallyProcessNode(Node node) {
    if (node.isQuestionable()) {
        NodeData nodeData = node.getNodeData();
        if (nodeData.isBad()) {
            return;
        }
    }

    processNode();
}

Even better is to factor out the investigation:

for (Node node : nodeList) {
    if (shouldProcessNode()) {
        processNode();
    }
}

boolean shouldProcessNode(Node node) {
    if (node.isQuestionable()) {
        NodeData nodeData = node.getNodeData();
        if (nodeData.isBad()) {
            return false;
        }
    }

    return true;
}

But isn’t return also a goto? Yes, but it’s much clearer where it’s going: it’s completely leaving the area. You don’t have to look carefully at the enclosing blocks until you find a loop that’s being continued.

You may be tempted to instead stuff it all into a single if statement:

for (Node node : nodeList) {
    if (!node.isQuestionable() || !node.getNodeData().isBad()) {
        processNode();
    }
}

But that’s the hardest of all to read. In this particular case I might be okay with a no-op documented block:

for (Node node : nodeList) {
    if (node.isQuestionable() && node.getNodeData().isBad()) {
        // Skip this node.
    } else {
        processNode();
    }
}

That logic is easier to understand, but the situation is often not so simple. Breaking it out into a separate function has the additional advantages of allowing you to name it and document it. In any case all of the above are better than the version with continue.