5 ms·
I'm very surprised that early returns are still commonly used and advocate in 2019. In my opinion, they are obviously terrible. I'll try to convince you. First
by quadcore 7y ago
I'm very surprised that early returns are still commonly used and advocate in 2019. In my opinion, they are obviously terrible. I'll try to convince you.
First the practical, in my experience, a big function with many early returns has a bug or has a convoluted logic, 100% of the time. I've literally made a living and a career by untangling big functions with early returns. I've really saved many situations and code just by doing that. The reason this happens is that early returns are gotos and gotos are harder to follow.
Also, you can't move easily (think copy/paste) code than has early returns, which means that the code is defacto harder to refactor. So in practice, code with early returns stays in place and grows.
Early returns are artefacts of the assembly jump instruction. They shouldn't even be in a high-level programming language because it makes no sense to have jumps in such a language. They make the programmer lives in the assembly language world (succession of instructions that update a state) instead of the wonderful world of high-level programming (expressions).
If you interested by what I'm saying, you should try removing all returns, continues and breaks from your code, you'll see what wonderful things happens. It's literally magic. Things becomes so much more expressive without them. Which is the point, high level programming is about gaining more expressiveness over assembly code.
Early returns is one of the big pillars of bad inexpressive entangled convoluted programming. And what is people reason for using early returns? It makes less indentations...
You may wonder why early returns doesn't sound that much like a big deal after all. And you are right, there is no big deal. No big deal in terms of what the market reward. Now if you care about the art of programming, this is a huge deal imo.
edit: thank you for the fair discussion. I would like to insist that obviously, if your code is more expressive, you will take better decisions and at the end, what I'm saying is that you will end up with a totally different program. Early return or not is not about cosmetic. It's about writing better program.
- ubercow13 7y agoHow would you rewrite the first example?
- quadcore 7y agoreturn rawData && rawData.length > 1 ? rawData.map((item) => item) : [] In many other cases, I would use a result variable. It's more expressive and more importantly you can move that code (the result variable may become just another variable in a bigger function). (note that in many programming language like lisp or haskell you wouldn't need the result variable because everything is an expression) Granted, it looks like no big deal in many many spots. Until it is, particularly at scale when you can't refactor anything because everything is full of early returns.
- deleted 7y ago[deleted]
- Buttons840 7y agoYour program can focus on executing statements, or it can focus on evaluating expressions (or a mix of both), and I would agree there are pros and cons to each. These small concrete examples are good to talk about. How would you rewrite the following: def contains_3(xs): """Returns True if the given list contains a 3.""" for x in xs: if x == 3: return True return False It does use an early return, but it is not long or convoluted. You could use something like `any()`, but I would never find fault with the above function or attempt to refactor it. It is as clear and efficient as possible, and uses basic language features even beginners would be familiar with.
- quadcore 7y agoThis is how I write it: def contains_3(xs): """Returns True if the given list contains a 3.""" found = false for x in xs && !found: found = x == 3 return found Not a valid python code but who cares. Using proper python, you could write the whole thing in one line using list comprehension. In terms of expressiveness (as much as this short example needs expressiveness), note how found is x == 3 which is what you want right. Note how the loop condition is now full. One could also note how every line is about found so you can instantly tell what lines is about found, which one is about foo or bar, if you'd picture a bigger function. Note how there is now only one condition (the loop) and not two (loop and if) (in many situations you would end up with less lines of code, surprisingly). Finally, note how DRY this is: you return x == 3, not True if x == 3 is True (there is many cases where this DRY property is more clearly visible). This code reads like a list comprehension. In addition to be more expressive (although, again, this is more striking on a bigger function) and movable, you can also actually write bigger functions like that which you can't with early returns (everybody agrees this is a mess). If I'd tell you not to use jumps, you would have found that solution. Simply ban jumps from your code (find the alternative way of writing things) and you will obtain a code that will surprises you, especially in bigger functions.
- rrobukef 7y agoI'm also a fan of early exits, for more reasons than in the post: an early return results in less nestings and less variables, it makes the code simpler. Consider: Option<T> sink(int pos) { if(pos>=this.length()){ return Nope; } // code that has depends on the bounds check Some(value) } Versus: Option<T> sink(int pos){ Option<T> ret = Nope if(pos < this.length()) { // same code ret = Some(value) } return ret; } I argue that the second is more complex: an added variable, deeper nesting, useless return line. When rewriting multiple guards you get more empty statements that reassign the default value of the return. I do have the following opposite opinion too: guards should be simple: no loops, no complex control flow (name the loop). No side effects in guards. And no guards handled by the expected case, even for clarification.
- quadcore 7y agoreturn pos < this.length() ? Some(value) : Nope See, mine is already way more simple than yours. This is my point, if you force yourself not to use jumps, you will end up with simpler and better programs. Also the second example you wrote is expressive and more importantly you can move that code. Also note that in many other programming language like lisp and haskell, you would never need the intermediate variable as you can basically write return if...
- epse 7y agoBut that requires you to have the output assigned to a variable even without valid input..
- quadcore 7y agoThis whole guard/validation thing is a fabrication of the mind. Because early returns do returns something on an invalid input. If anything, those validations should be asserts/exceptions.
- rrobukef 7y agoThe comment twice stands for some complex logic with side-effects which calculates value. You shouldn't put that in a ternary if, in most languages you can't. And even then I would reverse the order, placing the exceptional and short case first. Moving the code: why? You have a perfectly encapsulated function. If you need to, you have more problems with shadowing variables, functions and with return types. For functional languages: doesn't Haskell explicitly have guard pattern? I thought the general guidelines favored matching and guards over if statements.
- fjfaase 7y agoAs, with everything, it should be applied wisely. In most cases, I think it is perfectly fine to have early exits on parameter checks at the start of the function/method. Otherwise you end up with deep nested if-then-else or complex conditional expressions. And yes, having multiple exits in complex functions, is a bad code smell. Often indicating that the function needs to be refactored, split is smaller functions. I also prefer to use early exits (also with break), if I can avoid the introduction of an Boolean variable. Returns, gotos, breaks, and continues are not in themselves bad. It is people who do not know how to use them wisely, are bad at software engineering.
- quadcore 7y agoAs, with everything, it should be applied wisely. Early returns is not a feature of the language. It's an artefact of assembly language. You have no reasons to use it in the first place. In most cases, I think it is perfectly fine to have early exits on parameter checks at the start of the function/method. I believe you'd be surprised what aberrations you'd find in your code and all the simplifications you could make if you would removed all the jumps.
- solarkraft 7y agoAre early returns essentially equivalent to gotos? I suppose I could get rid of early returns with a variable and breaks, but is it worth it? My functions tend to be small enough for me to not run into problems with early returns.
- quadcore 7y agomassively edited. very interesting, thanks for the comment They are literally goto end. It used to be done like that a lot actually. I suppose I could get rid of early returns with a variable and breaks, but is it worth it? Very good question. My claim is that yes it is tremendously worth it if you care about great code. The reason is because by introducing intermediate variables (or by using more the ternary operator ?) you will have a more expressive code, and then you will subsequently take better decisions when you will write/maintain/refactor it. And more importantly, you will refactor it because you can (how do you move code that's full of early returns?). At the end, your program will be much simpler and better (less bugs, faster, etc). Just try it in a big-enough program, you will not come back to the old ways, I think. My functions tend to be small enough for me to not run into problems with early returns. Very interesting. This point would require a live discussion. Many things are at play here, between over engineered object-oriented programs where everything is broken into little pieces that shouldn't (most of the time you should make a function when it's called twice, not because it represent a world object) and also, I'm not surprised you guys don't do bigger functions since you fill them with early returns. In clear, what I'm saying is: are you guys not able to write any big functions because of the early returns? I wasn't expecting that, sounds almost plausible. You end up breaking things up way too much, over engineering things and all just to avoid bigger functions because you can't have them because of the early returns? Very very interesting. Of course this is complete conjecture and certainly not true. Or is it? edit: it also depend on what you mean by worth it. In terms of market value, you will not be rewarded because the market the doesn't reward great code so much anyway, broadly speaking.
- Buttons840 7y agoIf what you say is true, a mechanical transformation done by a simple soure-to-source compiler could improve code by turning early returns into nested if/then statements. The code would be longer and more nested, but (you claim) better.
- quadcore 7y agoWrong, because once you would see the code with the if statements, you would discover bugs and that the logic is convoluted. You would refactor it into a much simpler program.
- Buttons840 7y agoWe can all agree that convoluted logic is bad, but there are functions with early returns which are not convoluted.
- nosianu 7y agoExactly! Just add early returns when appropriate and all becomes much clearer. Also beware of people with strong opinions about way too general things in areas where specific things are.. specific, and where discussions of the sort you started are nearly almost a bad sign. Without something concrete to solve/look at these discussions are more religious in nature. You can say whatever, no way to prove or, worse, disprove. When people get into heated discussions there is a good chance that there is no practical test possible because the discussion is not about anything concrete but about "ideas" - where everybody has different ones for the same keyword(s).
- jacobolus 7y ago> try removing all returns, continues and breaks from your code, you'll see what wonderful things happens. It's literally magic. Things becomes so much more expressive without them LOL. Try adding returns, continues, and breaks where appropriate to some random code that doesn’t currently use them, and you’ll see what wonderful things happen. It’s literally magic. The control flow becomes clearer and more linear and the code becomes more logical and concise, with fewer weird conditionals repeated several times for no reason. Also often improves performance, “for free”.
- quadcore 7y agoI guess I should explain that expressiveness leads to better decisions which leads to better code. In short, what I'm saying is that you will end up with a totally different program.
- amelius 7y agoBut break/continue statements are also like goto, and to be consistent they would need to be eliminated as well.
- nextmemorandum 7y ago>a big function with many early returns And how would putting these early returns in a gigantic if condition or several increasingly indented if blocks fix these bugs? It will not fix any bugs since both forms are equivalent. >early returns are gotos All types of control flow can be represented with goto. >gotos are harder to follow Harder to follow than what? The majority of programmers are familiar with following gotos by using an abstraction over it such as using loops or if blocks. >You can't move easily (think copy/paste) code than has early returns If code is able to be moved to or be copied into another part of the codebase, this is a signal that the piece of code should be put in a function. If you are talking about using a subset of code following early returns, then you do run into the problem in not knowing what subset of early returns (aka preconditions) that are needed to bring be in a new function. This is the same with using large or nested if blocks. >Early returns are artifacts of the assembly jump instruction By this logic all control flow constructs are artifacts of that. Regardless whether or not you use early returns, you get equivalent assembly code after compilation. >it makes no sense to have jumps in [a high level] language I disagree. It makes perfect sense to have jumps in a high level language. A high level language should allow you to write abstractions over these jumps though. This way you can name and reuse patterns of jumps in your program. In addition you can think of early returns as a high level construct too instead of a low level one. You can think of if blocks as a monad. If we take a look at the article and used nested if blocks you could think of the type of the function as If If List (we can imagine that there is an interpreter behind the scene to convert this into a plain List). Since If is a monad it means we can do the join operation. This would turn it into a single If List. The obvious way to do this transformation by hand to the source file it would be equivalent to merging both if statements into a single one. If the else statements would return different results you would need to make sure you return the value associated with how far into the combined condition you get. Doing it this way is a little awkward. Also remember that monads give us the bind operation too. If we manually transform the source into using the bind operation we actually get the same thing as using early returns. Using early returns is a much clearer implementation of handling the conceptional If monad. By using the If monad we are only having to deal with a single if statement / condition at a time. >Things becomes so much more expressive I beg to differ. They are equally expressive, but early returns allow you to mention the preconditions / special cases all at the beginning of your function. >Early returns is one of the big pillars of bad inexpressive entangled convoluted programming I'm not sure how early returns make something entangled. Since they can only be at the beginning of a function, they can't really be entangled in your whole function. >And what is people reason for using early returns? It makes less indentations Yes, this is a side effect of being a monad. It means we can focus on a single if statement at a time instead of focusing on a bunch of nested if statements at the same time. This is the same thing as when javascript developers realize that converting callbacks that call callbacks, etc into using promises reduces the nesting that is needed. In summary, I am not convinced from your argument that early returns are bad since they are an abstraction over assembly.
- DogLover_ 7y agoEarly returns is the best pattern I ever adapted. Because of it I rarely if ever use 'else' expressions anymore which leads to less convoluted code. It seems we have different experiences because where I see early returns used is mostly where functions are already small.
- quadcore 7y agoOr is it that you (guys) can't have bigger functions because you impulsively fill them with early returns? So you end up over-engineering things just to avoid bigger functions (because you can't have them because they are filled with early returns). Real question, I don't have the answer, but that question is my take from this discussion.
- DogLover_ 7y agoIf you don't use early returns don't you end up with functions doing lots of state/variable mutation (within the function)? Thats what I consider the problem with big function and/or not using early returns. You then need to keep track of all those modification. I find it much easier to reason with early returns. You can certainly over-engineer things just to avoid bigger functions. However from my experience I see that far less than complicated large functions with little care to readability.
- quadcore 7y agodon't you end up with functions doing lots of state/variable mutation (within the function)? Good question. This is true, but this is a programming language bug. In some programming language you can write the equivalent of return if... or return for.... Also note that those intermediate variables add a lot in expressiveness (you're computing result or screensize etc). The mutation is unfortunate indeed but a programming language bug.
- nickjj 7y agoI used to use early returns a fair bit in Python and Ruby (my primary languages). But lately I've been working with Elixir which doesn't support early returns and while Elixir does have some tricks up its sleeve to better handle the idea of having an early return without using it (such as pattern matching on function arguments and / or guards), I'm now slowly shifting into thinking early returns in any language makes things more difficult to reason about in the end most of the time. I say "most of the time" because for very short functions where you might return early to avoid an else clause or deal with varying types of input, that's not too bad. It's really a problem in the use case you explained where you have a larger function that might have 4 or 5 early returns scattered about and that function might be nothing more than having a few short if statements that say "if this, then do_something and return". It becomes difficult to maintain because now the function ends in 5 different spots based on 5 different conditions.
- quadcore 7y agoAn interesting conjecture I've made during this discussion is the following: can early returns be responsible for over-engineered code? Think about it. If it's acceptable for programmers to use early returns then they can't stand bigger functions because those big functions systematically end up full of early returns. So programmers breaks things into small pieces instead, systematically. While there is many reasons why breaking things into small pieces could be the right thing to do, early returns is certainly not among them. You have to picture what I'm saying. What I'm conjecturing here is that many programmers simply can't make bigger functions so they'll take a battery of bad decisions, unconsciously, to avoid them. How big is that?
- nickthemagicman 7y agoHow would you replace early returns? If statements?