News for hackers. Stuff that matters.
Re:Programming Patterns I Like by quadcore on Saturday June 29, 2019 @02:24AM (#20311080)
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.

Re:Programming Patterns I Like by fjfaase on Saturday June 29, 2019 @03:01AM (#20311199)
As, 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.
Re:Programming Patterns I Like by quadcore on Saturday June 29, 2019 @03:13AM (#20311246)
As, 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.

Re:Programming Patterns I Like by irjoe on Saturday June 29, 2019 @03:44AM (#20311337)
I'm currently writing a lot of Elixir where early returns are a feature of the language (https://hexdocs.pm/elixir/Kernel.html#module-guards).

I also write a lot of JS and tend to use guard clauses fairly liberally. I find they make boundaries (x < 0) much clearer. My functions are also < 10 lines in most cases. For me it leads to less convoluted code but YMMV.

Re:Programming Patterns I Like by sterwill on Saturday June 29, 2019 @08:45AM (#20312271)
I love Elixir, but I don't think of guards as "early returns". They're more like preconditions or filters: they prevent the function from running at all if they don't match, and because they're applied top-down in the module, it's really easy for the programmer to find the one that should match. They're great!

Other programming languages let you return from any line in the function, no matter how deep in a conditional, inside nested loops, before or after variable side effects, etc. Tricky to know just _what_ that function will do given some inputs.

Re:Programming Patterns I Like by irjoe on Saturday June 29, 2019 @09:36AM (#20312489)
You're right, they're not really "early returns" but for me at least they are often in similar fashion to the "early exit" pattern in the article:

    def transform_data(raw_data) when length(raw_data) == 1, do: []
    def transform_data(raw_data) do
        # actual function code goes here
    end
That said, I wouldn't want to see the above snippet in my team's codebase :)
Re:Programming Patterns I Like by rrobukef on Saturday June 29, 2019 @04:08PM (#20314501)
Not a fan of prolog then? As an excercise, find the guard in this snippet (https://rosettacode.org/wiki/Babbage_problem#Prolog) and imagine such code in a team codebase!
Re:Programming Patterns I Like by irjoe on Sunday June 30, 2019 @05:19AM (#20317225)
Quite the opposite :) My preference would be to rely on pattern matching (unification) in the function head:

    def transform_data([_|[]]) do: []
I just wanted an example of using guard clauses. It does depend on the actual code though, there's probably a more elixiry solution.
Re:Programming Patterns I Like by solarkraft on Saturday June 29, 2019 @03:29AM (#20311292)
Are 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.

Re:Programming Patterns I Like by quadcore on Saturday June 29, 2019 @03:36AM (#20311309)
massively 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.

Re:Programming Patterns I Like by kd5bjo on Saturday June 29, 2019 @05:41AM (#20311669)
> They are literally goto end.

So are a lot of other constructs:

break is goto the end of the loop, continue is goto the beginning of the loop, the closing of a loop block is goto the beginning of loop, else is goto the end of the conditional, if is maybe goto the next else, and return (early or not) is pop from the stack and goto that address.

The fact that a language feature uses a JUMP instruction does not make it necessarily evil; most of them do. Everything has both benefits and drawbacks which are often highly situational.

You may find it easier to convince people of the problem with early returns if you limit the scope of the argument to a more specific context than “always”: it’s usually easier to make an argument for a concrete case rather than an abstraction, and then show how the same reasoning applies to a large class of situations.

Re:Programming Patterns I Like by solarkraft on Saturday June 29, 2019 @01:19PM (#20313665)
Thanks a lot for the elaborate reply. I do tend to break things up into small functions. Why is that over-engineering? It makes it easier for me to reason about my code. It also makes it easier to refactor sometimes, although of course you're now restrained by the function's construct, so it becomes more difficult to move things between functions.

Do I understand correctly that you tend to make large functions because they're easier to refactor (and not returning early makes this easier)?

Re:Programming Patterns I Like by rrobukef on Saturday June 29, 2019 @02:58AM (#20311183)
I'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.

Re:Programming Patterns I Like by quadcore on Saturday June 29, 2019 @03:03AM (#20311207)

  return 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...
Re:Programming Patterns I Like by rrobukef on Saturday June 29, 2019 @06:29AM (#20311797)
The 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.

Re:Programming Patterns I Like by epse on Saturday June 29, 2019 @03:35AM (#20311305)
But that requires you to have the output assigned to a variable even without valid input..
Re:Programming Patterns I Like by quadcore on Saturday June 29, 2019 @03:39AM (#20311322)
This 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.
Re:Programming Patterns I Like by jacobolus on Saturday June 29, 2019 @03:40AM (#20311325)
> 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”.

Re:Programming Patterns I Like by quadcore on Saturday June 29, 2019 @03:49AM (#20311352)
I 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.
Re:Programming Patterns I Like by DogLover_ on Saturday June 29, 2019 @04:25AM (#20311451)
Early 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.

Re:Programming Patterns I Like by quadcore on Saturday June 29, 2019 @04:32AM (#20311466)
Or 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.
Re:Programming Patterns I Like by DogLover_ on Saturday June 29, 2019 @05:07AM (#20311579)
If 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.

Re:Programming Patterns I Like by quadcore on Saturday June 29, 2019 @05:14AM (#20311601)
don'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.

Re:Programming Patterns I Like by nickjj on Saturday June 29, 2019 @06:12AM (#20311746)
I 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.

Re:Programming Patterns I Like by quadcore on Saturday June 29, 2019 @06:23AM (#20311778)
An 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?
Re:Programming Patterns I Like by ubercow13 on Saturday June 29, 2019 @02:43AM (#20311145)
How would you rewrite the first example?
Re:Programming Patterns I Like by quadcore on Saturday June 29, 2019 @02:48AM (#20311161)

   return 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.
Re:Programming Patterns I Like by Buttons840 on Saturday June 29, 2019 @11:45AM (#20313213)
Your 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.
Re:Programming Patterns I Like by quadcore on Saturday June 29, 2019 @01:27PM (#20313704)
This 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.

Re:Programming Patterns I Like by Buttons840 on Saturday June 29, 2019 @02:58PM (#20314144)
> you would have found that solution

I don't think so, even after seeing your pseudo code don't see a nice way to avoid the early return. I don't understand how your for-loop / if-condition mix works, but let me try to translate it as best I can into valid Python:

    def contains_3(xs):
        found = False
        index = 0
        if not found:
            found = xs[index] == 3
            index += 1
        return found
This was honestly my first best effort attempt at creating this function without an early return. But it is wrong, it only checks if the first value of the list is 3. My opinion is that this style is more error prone (and indeed, I did create an error), and objectively there is more code and more state and thus more opportunities to get things wrong.

    def contains_3(xs):
        found = False
        index = 0
        while not found:
            found = xs[index] == 3
            index += 1
        return found
That works, but again, there is more code and more state. When reading this code I must ask myself "are they iterating over the list correctly?" and I must spend mental effort answering that question. This doesn't happen with a for-loop.

I don't understand why you keep talking about jumps in assembly code. Code which has zero early returns will still have hundreds of jumps in the assembly code.

Code that is short is good. Code that is readable is good. I don't think we'll agree on what code is more readable, but we can at least agree on which code is shorter.

As I've gained programming experience I care less and less about how individual functions are implemented and more and more about what those functions do and what they return. For something like `contains_3`, I see that it is a function with no side-effects, which returns a boolean, and wont fail (at least, wont fail in a language with type checking). This is a small simple function, and I don't care how it's implemented, it is a good function. If it becomes a problem for some reason, it is isolated and can be easily fixed in isolation.

Re:Programming Patterns I Like by Buttons840 on Saturday June 29, 2019 @03:06AM (#20311216)
If 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.
Re:Programming Patterns I Like by quadcore on Saturday June 29, 2019 @03:07AM (#20311221)
Wrong, 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.
Re:Programming Patterns I Like by nosianu on Saturday June 29, 2019 @07:54AM (#20312063)
Exactly! 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).

Re:Programming Patterns I Like by Buttons840 on Saturday June 29, 2019 @03:20AM (#20311273)
We can all agree that convoluted logic is bad, but there are functions with early returns which are not convoluted.
Re:Programming Patterns I Like by nextmemorandum on Saturday June 29, 2019 @03:49AM (#20311354)
>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.

Re:Programming Patterns I Like by dang on Saturday June 29, 2019 @03:21PM (#20314263)
You have to use two newlines to get a link break in HN (https://news.ycombinator.com/formatdoc). I added a bunch of those to your post to make it readable.
Re:Programming Patterns I Like by nextmemorandum on Saturday June 29, 2019 @10:57PM (#20316065)
Thank you. For some reason I was unable to edit my post to fix the issue myself.
Re:Programming Patterns I Like by amelius on Saturday June 29, 2019 @03:49AM (#20311353)
But break/continue statements are also like goto, and to be consistent they would need to be eliminated as well.
Re:Programming Patterns I Like by agumonkey on Saturday June 29, 2019 @05:04AM (#20311573)
The more I read about for loops, non local exits.. the less I want to go back to imperative programming.
Re:Programming Patterns I Like by quadcore on Saturday June 29, 2019 @04:52AM (#20311528)
If you interested by what I'm saying, you should try removing all returns, continues and breaks from your code
Re:Programming Patterns I Like by nickthemagicman on Saturday June 29, 2019 @09:30AM (#20312452)
How would you replace early returns? If statements?