Multiple return statements, and the single-exit rule
One exit or many. An argument old enough to have outlived the machines it was about, and still worth settling for yourself.
There is a rule, held firmly in some places, that a method should contain exactly one
return statement and that it should be the last line. It is worth understanding where it
comes from before deciding whether to follow it, because the reasoning is real and the
conclusion is mostly wrong.
Where the rule comes from
Strict structured programming treats a method as a single block with one way in and one
way out. If control leaves only at the closing brace, you always know where to look for
the exit, and reasoning about what is true when the method finishes gets easier. The same
argument is made against break and continue in loops, and it is the same argument:
fewer exits, less to hold in your head.
That is not nothing. On a long method with several nested blocks, a return buried in the
middle really can be missed.
What it costs
The trouble is what happens when you enforce it. Returning early is usually the clearest way to say “we are done here”, and removing it means introducing a variable whose only job is to carry the answer to the bottom of the method.
Take a small example — classifying a number as negative, zero or positive.
Returning as soon as the answer is known:
public static String classify(int n) {
if (n < 0) {
return "negative";
}
if (n == 0) {
return "zero";
}
return "positive";
}
The same thing forced into a single exit:
public static String classify(int n) {
String answer; // exists only to carry the value to the bottom
if (n < 0) {
answer = "negative";
} else if (n == 0) {
answer = "zero";
} else {
answer = "positive";
}
return answer;
}
Both are clear at this size. The second is longer and introduces a mutable local for no reason other than the rule. Scale the method up — several conditions, a loop, a couple of error cases — and the single-exit version needs flags to remember which case it is in, and those flags have to be set and tested correctly in every branch. The rule that was meant to make control flow obvious ends up hiding it inside boolean state.
Notice that the first version needs no else at all: once a branch returns, everything
after it is already the other case. That is usually the tidiest form, and it is the shape
guard clauses take.
There is also the conditional operator, which is shorter and which people reach for to look clever at least as often as to be read:
return n < 0 ? "negative" : n == 0 ? "zero" : "positive";
Nested like that it is worse than either version above. One level is fine; two is a puzzle.
Exceptions make the strict reading impossible
The argument has a hole in it that is rarely acknowledged: return is not the only way
out of a method.
- A
throwexits it. - So does any call to a method that throws — and if what it throws is unchecked, nothing in the signature tells you it can happen.
- Any statement that can raise an exception at all is therefore a potential exit point.
A method with one return and ten statements that might throw does not have one exit. It
has eleven. Counting return statements measures something narrower than the thing the
rule claims to be about.
Where this leaves it
Guard clauses at the top of a method — reject the bad input, return the trivial case — and then one main path is easier to read than the same logic flattened into a single exit, and that is the pattern most codebases have settled on.
The honest version of the rule is not “one return”. It is make it obvious where the method ends and what it gives back. Some editors will highlight every exit point in a method, which serves that goal better than bending the code to satisfy a count.