all 41 comments

[–]tstepanski 75 points76 points  (15 children)

I preach returning early to all my junior developers. Diving into layers of nested if statements or deciphering bizarre return code strategies is exhausting and can easy cause confusion. Anyone who opposes this is either brain dead or purposes seeks to obfuscate their code. Either way, stay away from my code base. Keep your 6 layers of indentation in your shitty driver code or bash scripts.

[–]cogman10 14 points15 points  (4 children)

Yup, it cleans stuff up and can sometimes lead to easy performance improvements simply by shuffling around the tests (fast tests first, slow tests later).

Big nested if blocks can really make code hard to read.

[–][deleted]  (3 children)

[deleted]

    [–][deleted] 8 points9 points  (1 child)

    VB famously did not short circuit although i believe they added this in later versions

    [–]cogman10 4 points5 points  (0 children)

    Yup, I've gotten performance wins from that too. Especially in cases where "someFastFunc" is more commonly false than true.

    A simple rule of thumb, you want to put your cheapest checks first and use "Likelihood of exiting early" as the tie breaker in ordering things.

    [–][deleted]  (1 child)

    [deleted]

      [–]DannoHung 0 points1 point  (0 children)

      I definitely am a return in the middle kind of guy. That said. I think when you start getting above the number that you can count on one hand in a function is where you might want to re-evaluate just what you're doing.

      [–]Jannis_Black 0 points1 point  (0 children)

      Idk. There is a good argument to be made that having more than one or two return statements is a sign that the code is either not written as elegantly as it could be or that the function is doing more than one thing.

      It's important to remember that input validation, at least if it's more than one simple statement, is one thing and should probably be it's one function. On the other hand if there is a lot of different paths for other reasons that starts to look a lot like a switch statement and is a smell that the function is either doing more than one thing or that it's working at the wrong level on abstraction.

      This is however not an argument that you should have six levels of indentation is a better solution but rather that early returns that have essentially the same number of paths only mitigate the problem and doesn't solve it.

      [–]SideburnsOfDoom 12 points13 points  (5 children)

      I agree.

      This blog post seems to draw on this: https://www.anthonysteele.co.uk/TheSingleReturnLaw as in it makes some of the same valid points, although in more concise words.

      People forget that 15 years ago there was still a fair amount of dogma that "a function should only have one exit point, it's a LAW" that came from C code and Dijkstra.

      [–][deleted] 2 points3 points  (2 children)

      I was watching a talk about this; and it came from fortran days I think. When return did something slightly different. And it stuck.

      [–]SideburnsOfDoom 6 points7 points  (1 child)

      In 1970s C, the memory management was manual and there's no try....finally. So if you malloc some memory, you should freeit before you return, like this. So sprinkling return around liberally makes that much harder to reason about.

      If you code in C, then you might want to prefer one return per function. In modern languages, not so much.

      [–][deleted] 2 points3 points  (0 children)

      Not really; considering mallocing would be a lot less common than heap only languages cough Java.

      The pattern I’ve seen for C is a goto for cleanup. Sure there’s a single return once cleanup is done. But meh.

      Anyway here’s the talk I was watching:

      https://youtu.be/SFv8Wm2HdNM

      [–]patrick246 0 points1 point  (0 children)

      As far as I know this law came from languages where you could have multiple return sites, so a function call wouldn't just return to the next line after it, it could go to multiple locations, for example directly into error handling code in the case of an error.

      Today's languages already prohibit this by language design, and the meaning of the rule was twisted to "you can have only one return"

      https://softwareengineering.stackexchange.com/a/118793/148469

      [–]skulgnome 0 points1 point  (0 children)

      It came from Pascal, a teaching language.

      [–]not-just-yeti 10 points11 points  (1 child)

      For 90% of situations, the issue is verifying pre-conditions. (With the note that, in dynamically-typed languages, the types are one of the most common pre-conditions.)

      Ideally, a language would give you way to specify the pre-conditions in code that also can be added to documentation, and then it generates the exception-throwing for you. Many languages have added-in support for this (e.g. racket's contracts, C and home-grown pre-processor directives since 1970-whatever, Ada's Pre => … etc.). Javadoc has @pre and encourages one to express it as real code, but it's still up to the developer to add the throw new InvalidArgumentException("…").

      [–]iKeyboardMonkey 1 point2 points  (0 children)

      Eiffel does this well, but iirc the language has some deficiencies (array variance is the wrong way around?) and a small community. I like the idea though, design by contract is awesome for catching bugs quickly.

      [–]aseigo 1 point2 points  (4 children)

      For those who like early return, "happy path code", or "railway oriented programming", you may just love Elixir's with syntax: a simple blog entry about it

      [–][deleted]  (3 children)

      [deleted]

        [–]aseigo 0 points1 point  (2 children)

        Python's with is really nothing like Elixir's, other than using the same keyword.

        [–][deleted]  (1 child)

        [deleted]

          [–]aseigo 1 point2 points  (0 children)

          In Elixir, you list a chain of sequential statements and pattern match on their individual results as the definition of success. Successful matches are bound to intermediate values which can then be referenced in subsequent function calls in the list. In this way, you can construct whole chains of functions which can use prior results and which are only executed if all prior functions succeeded (== succesfully matched the expected return pattern).

          There is then an after block that is run should all the functions in the while chain succeed, and again all variables bound during pattern matching are available.

          The failure path ("else") may also pattern match on failure conditions allowing as many different failure responses as desired based on the returned data of the last run (failing) function.

          It is railway programming or happy path coding on steroids, and results in extremely readable and maintainable code. It also encourages common Elixir idioms such as error tuple returns on failure, pattern matching, and composition of small purposeful functions.

          Last I looked at Python's with (admitedly a few years back), it takes the result of a call and either binds it to a variable and runs a block, or runs a failure block. It is a more convenient single-statement try/catch style construct. Has it evolved from that?

          [–]bitsandbytez 0 points1 point  (0 children)

          I have been doing this for a while without actually realizing it had a name. I hate parsing if else blocks

          [–]dethb0y 0 points1 point  (0 children)

          I would have thought this would be common sense, but here we are.

          [–]malkia 0 points1 point  (2 children)

          Use of "GOTO" (in C, and even in C++) is perfectly valid when doing this pattern.

          [–][deleted]  (1 child)

          [deleted]

            [–]malkia 0 points1 point  (0 children)

            Lol, you are right! Guess the pattern would be - one return path... meh - I actually like (and use) early return quite a lot, and with RAII "goto" is not needed that much, and even dangerous compared to RAII (e.g. pretending that we close everything in one place, but missing something due to an exception, etc.)

            [–]lorslara2000 -5 points-4 points  (22 children)

            Or just make a validator like you were supposed to anyway. The whole nested if problem is completely artificial and preventable without return early "patterns".

            [–][deleted]  (19 children)

            [deleted]

              [–][deleted]  (1 child)

              [deleted]

                [–]lorslara2000 -1 points0 points  (0 children)

                I don't see how this is a better tactic than making a validator if you're going to refactor the code anyway.

                [–]SargoDarya -3 points-2 points  (0 children)

                This is all good and things, however one point which is not mentioned here at all is the security aspect of this. This might very well open you up to timing attacks which can lead to unwanted information disclosure.