“No-else-after-return” considered harmful(blog.mozilla.com)
blog.mozilla.com
“No-else-after-return” considered harmful
http://blog.mozilla.com/nnethercote/2009/08/31/no-else-after-return-considered-harmful/
13 comments
I'm with Mozilla.
If I have a function with multiple conditions that cause me to return error / false / nil, I'd rather not have to nest them all in a series of if/then/else statements nor combine them into a single more-complicated if condition. I'd rather write them each as separate if-and-returns, followed by the real meat of the function.
function updatePlayers{
I think this better spells out the "error" conditions.
If I have a function with multiple conditions that cause me to return error / false / nil, I'd rather not have to nest them all in a series of if/then/else statements nor combine them into a single more-complicated if condition. I'd rather write them each as separate if-and-returns, followed by the real meat of the function.
function updatePlayers{
if (charList is empty)
return false;
if (play is paused)
return false;
if (char is frozen)
return false;
//do stuff here
return true;
}I think this better spells out the "error" conditions.
You'd be right, of course, but the original article concedes this point. He's talking about situations like
if (a < b)
return a
return b
vs. if (a < b)
return a
else
return b
vs return ( (a < b) ? a : b)What is the distinction between those two situations? (i.e. those in which the article "concedes the point"?)
Presuming this is a genuine question: the difference is whether one goes on to do more work after the 'guard' return, or whether one is just choosing which value to return.
I think the style guide is being silly, and there is no simple rule that says "always use an else" or "never use an else". I'd see "Avoid unnecessary indentation" as a better rule.
I think the style guide is being silly, and there is no simple rule that says "always use an else" or "never use an else". I'd see "Avoid unnecessary indentation" as a better rule.
One's an early return due to error, one's a conditional return value.
Error is the wrong term, because you don't return after an error condition, you raise an exception. Perhaps stop condition is more appropriate?
I always go a step further, and use at most one "return" in a function.
I find short-cut "return" statements frustrating for many reasons. For example, there's a risk that proper cleanup will not always happen, it is harder to see state and program flow when debugging, and it's no longer simple to make every-case changes by adding things to the end of the function. They're really just another type of "goto".
The only "advantages" I can think of are saving a few keystrokes, and having slightly better performance by not using a variable for the return value. In my experience, neither of those things has ever mattered.
In other words, I'd do this:
I find short-cut "return" statements frustrating for many reasons. For example, there's a risk that proper cleanup will not always happen, it is harder to see state and program flow when debugging, and it's no longer simple to make every-case changes by adding things to the end of the function. They're really just another type of "goto".
The only "advantages" I can think of are saving a few keystrokes, and having slightly better performance by not using a variable for the return value. In my experience, neither of those things has ever mattered.
In other words, I'd do this:
<type> result = defaultValue;
if (condition)
{
result = val1;
}
else
{
result = val2;
}
return result;
With this approach, there is never anything after the "return", "else" or otherwise.Your cleanup argument holds true for C and its ilk, but not for anything else. In C you DO have to do manual cleanup, but in any language with exceptions you can (and should) do that in a finally block. Or in C++, via one of the many scope-aware smart pointer classes. In higher level languages, it is idiomatically correct to have many exit points from a function.
As for being harder to debug... it depends. Again, if you MUST do manual resource deallocation, then I can see your case. Having duplicated cleanup code inside the control flow gets old fast. But if you've got exceptions, then such a rule leads very quickly to unnecessarily deep, convoluted control structures. I've flattened many such structures by a) factoring chunks out to separate functions and b) returning early when appropriate.
It is very common for a function/method to either throw an exception or return a default value based on some parameter. In these cases, it's pound-you-in-the-face clear to have a list of "if(check) return whatever;" clauses at the top. If you do this before resource allocation, you don't even need to worry about cleanup. This feels like a much "purer" way to write a function to me, almost like applying pattern matching before falling back to imperative mode.
As for being harder to debug... it depends. Again, if you MUST do manual resource deallocation, then I can see your case. Having duplicated cleanup code inside the control flow gets old fast. But if you've got exceptions, then such a rule leads very quickly to unnecessarily deep, convoluted control structures. I've flattened many such structures by a) factoring chunks out to separate functions and b) returning early when appropriate.
It is very common for a function/method to either throw an exception or return a default value based on some parameter. In these cases, it's pound-you-in-the-face clear to have a list of "if(check) return whatever;" clauses at the top. If you do this before resource allocation, you don't even need to worry about cleanup. This feels like a much "purer" way to write a function to me, almost like applying pattern matching before falling back to imperative mode.
That's true, and I do use that approach ("RAII") in languages like C++. It definitely makes cleanup less of a problem. It also means that setup should be done no earlier than necessary, declaring variables very close to their first use (otherwise, a setup/cleanup cost may be paid for no reason).
Exceptions act like early returns themselves, but I will use them for truly exceptional cases (only). In other words, if a function can return 3 values, I consider those the "normal" cases that should weave through to the end of the function; if the function runs out of memory, I think that's an exceptional case that can return immediately.
Exceptions act like early returns themselves, but I will use them for truly exceptional cases (only). In other words, if a function can return 3 values, I consider those the "normal" cases that should weave through to the end of the function; if the function runs out of memory, I think that's an exceptional case that can return immediately.
I always go a step further, and use at most one "return" in a function.
I opt for one return per exit type. For example:
* If I have a success/failure function, I will have "return (0);" and "return (1);" statements.
* If I have a pointer-or-fail function (e.g., an object constructor), I will have "return (r);" and "return (NULL);" statements.
* If I have a true/false/fail function (e.g., check if a file exists; return 1 if it exists, return 0 if it doesn't exist, and return -1 if something failed such that we couldn't figure out if the file exists), I will have "return (0);", "return (1);", and "return (-1);" statements.
In trivial functions, this tends to make my code somewhat more verbose than necessary; but I've found that sticking to this coding style dramatically reduces the odds of me making mistakes when I come back to code later and modify it, since different exit types usually require different cleanup code.
I opt for one return per exit type. For example:
* If I have a success/failure function, I will have "return (0);" and "return (1);" statements.
* If I have a pointer-or-fail function (e.g., an object constructor), I will have "return (r);" and "return (NULL);" statements.
* If I have a true/false/fail function (e.g., check if a file exists; return 1 if it exists, return 0 if it doesn't exist, and return -1 if something failed such that we couldn't figure out if the file exists), I will have "return (0);", "return (1);", and "return (-1);" statements.
In trivial functions, this tends to make my code somewhat more verbose than necessary; but I've found that sticking to this coding style dramatically reduces the odds of me making mistakes when I come back to code later and modify it, since different exit types usually require different cleanup code.
Yuueurrgh.
Without early return, you wind up with multiple-nested if statements that make the code absolutely unreadable. Early return is almost always used as the error path -- if you need to perform cleanup, then instead of returning, use goto to jump to a single end-of-function cleanup handler.
There's nothing wrong with using goto for this purpose, and it means I can actually read -- at a glance -- code that requires more than a few preconditions at the start of the function.
Without early return, you wind up with multiple-nested if statements that make the code absolutely unreadable. Early return is almost always used as the error path -- if you need to perform cleanup, then instead of returning, use goto to jump to a single end-of-function cleanup handler.
There's nothing wrong with using goto for this purpose, and it means I can actually read -- at a glance -- code that requires more than a few preconditions at the start of the function.
Functions that have become too complicated should look ugly, so that they're likely to be split into simpler parts (for true readability).
When making that split, it is also helpful to have relevant cleanup code nearby: odds are everything is already in the inner block, instead of having to remove extra code at the end of the function.
When making that split, it is also helpful to have relevant cleanup code nearby: odds are everything is already in the inner block, instead of having to remove extra code at the end of the function.
That's the usual hand-waving argument (if you need this you're doing it wrong), but the fact is that even something with three simple preconditions is quickly rendered unreadable through three nested levels of if statements.
> if you need to perform cleanup, then instead of returning, use goto to jump to a single end-of-function cleanup handler.
That assumes that all of the cleanup is the same and that all of the relevant information that is available at the subroutine's top level.
That assumes that all of the cleanup is the same and that all of the relevant information that is available at the subroutine's top level.
I write this way, religiously, and literally every teammate I've worked with has been frustrated by it. I agree with all your arguments (and I'll add another one, which is that it makes the code itself easier to instrument --- which is why I started doing it), but I've been convinced not to try to convince others of it.
I tend to do this too, but I'm not convinced yet that I'm doing the right thing. I'd love to hear other opinions on this style.
Profoundly unconvinced.
A minor quibble. He claims that "no-elses" are prone to side-effects and "error-prone junk". However, "no-elses" are in my opinion as flagrant a crime as "no-braces" following if statements (which appears to be his style). The author says that he prefers the ternary operator (a > b ? a : b) to "no-elses"; however, its use is definitely more error-prone!
I prefer to return as early as possible and I dislike unnecessary else clauses. And unnecessary braces. I especially hate really long functions where there's only one return but a bunch of baggage and nested conditionals along the way to pull it off. Like mullr said, only handy if your language doesn't support guards.
Most of the people who code this way that I've met don't do it b/c they're trying to be 'pure.' Most of them say it was coding standard some manager mentioned somewhere else long ago and now they just do it w/o thinking.
Most of the people who code this way that I've met don't do it b/c they're trying to be 'pure.' Most of them say it was coding standard some manager mentioned somewhere else long ago and now they just do it w/o thinking.
On the one hand, this reads like "I prefer functional languages, and want to twist procedural languages into a form I'm more familiar with". On the other hand, I do find his re-written example more readable, and I'm not sure if that's due to my previous exposure to functional programming, some innate readability bestowed by the functional programming style, or just the fact that this particular code was rewritten by someone who wanted to make it a publishable example (as opposed to just getting it to work).
What's his argument about purity here? His examples rely on global state which is inherently impure. Rewriting them to make it more expression like does nothing to change that.
I think the main problem is the lack of braces means you have to rely on white space to figure out what the hell is going on.
That's the main reason why I religiously put open and close braces on even the smallest if/else blocks as it dramatically improves the readability of the code.
If the mozilla code style specified that you have to use braces no matter what then they wouldn't be having this problem.
Of course, I'm probably arguing with hackers who prefer to use constructs like 'return (a > b ? a : b)' rather than write readable code.
This is coming from someone who likes to put spaces after and before parentheses so it makes it easier to figure out what's going on.
e.g. if ( ( y > x ) || ( x < 10 ) ) - this makes things easier to read (for me) when you start to get a hell of a lot of nesting going.
That's the main reason why I religiously put open and close braces on even the smallest if/else blocks as it dramatically improves the readability of the code.
If the mozilla code style specified that you have to use braces no matter what then they wouldn't be having this problem.
Of course, I'm probably arguing with hackers who prefer to use constructs like 'return (a > b ? a : b)' rather than write readable code.
This is coming from someone who likes to put spaces after and before parentheses so it makes it easier to figure out what's going on.
e.g. if ( ( y > x ) || ( x < 10 ) ) - this makes things easier to read (for me) when you start to get a hell of a lot of nesting going.
The ternary operator is perfectly readable if you spend even the slightest amount of time getting used to it.
I like spacing my parens too, but what you wrote isn't as readable to me as:
I also like multiline expressions:
I like spacing my parens too, but what you wrote isn't as readable to me as:
( (y > x) || (x < 10) )
Spacing inside your terminal pair of parens only makes your atomic expressions harder to read.I also like multiline expressions:
if ( (x == 10) ||
(y == 10) ||
(x > y) )
{
.....
}Why do you put the operators for multiline expressions at the end of the preceeding line instead of at the beginning of the next line?
With statements, the first token on the line tells you what it's doing and the indentation tells you about its relationship to statement on the previous line. Why not do something similar for multi-line expressions?
With statements, the first token on the line tells you what it's doing and the indentation tells you about its relationship to statement on the previous line. Why not do something similar for multi-line expressions?
I like aligning the atomic conditional tests I'm or'ing together, and that's the most space-efficient way to do it. I guess I could indent the first line more and put the or operators on the left, though.
And here we have a classical bikeshedding article.
This sort of thing really doesn't matter much one way or another. Do what the rest of the codebase is doing, and move on with your life. If you're writing your own code, do what you feel like doing.
This sort of thing really doesn't matter much one way or another. Do what the rest of the codebase is doing, and move on with your life. If you're writing your own code, do what you feel like doing.
I disagree. The rule works for me.
I now find myself using 'cond' almost exclusively over 'if'.
And there is a little misunderstanding propagated: "Even in a language like C/C++ you can do something approaching functional programming by avoiding statements". I don't think so.
I now find myself using 'cond' almost exclusively over 'if'.
And there is a little misunderstanding propagated: "Even in a language like C/C++ you can do something approaching functional programming by avoiding statements". I don't think so.
[deleted](1)
Brendan Eich even said as much in the comments of the bug: he'd rather be programming in a pure, expression-based manner too, but C++ is not Ocaml. And for a lot of C++ code, no-else-after-return works reasonably well.