1330

I often talk to programmers who say "Don't put multiple return statements in the same method." When I ask them to tell me the reasons why, all I get is "The coding standard says so." or "It's confusing." When they show me solutions with a single return statement, the code looks uglier to me. For example:

if (condition)
   return 42;
else
   return 97;

"This is ugly, you have to use a local variable!"

int result;
if (condition)
   result = 42;
else
   result = 97;
return result;

How does this 50% code bloat make the program any easier to understand? Personally, I find it harder, because the state space has just increased by another variable that could easily have been prevented.

Of course, normally I would just write:

return (condition) ? 42 : 97;

But many programmers eschew the conditional operator and prefer the long form.

Where did this notion of "one return only" come from? Is there a historical reason why this convention came about?

fredoverflow
  • 6,854
  • 8
  • 39
  • 46
  • 1
    [this question is discussed at meta](http://meta.programmers.stackexchange.com/q/7876/31260) – gnat Feb 17 '16 at 07:13
  • 34
    This is somewhat connected to Guard Clause refactoring. http://stackoverflow.com/a/8493256/679340 Guard Clause will add returns to the beginning of your methods. And it makes code a lot cleaner in my opinion. – Piotr Perak Dec 14 '13 at 09:14
  • 15
    It came from the notion of structured programming. Some may argue that having just one return allows you to easily modify the code to do something just before returning or to easily debug. – martinkunev May 14 '16 at 11:39
  • 10
    i think the example is a simple enough case where i wouldn't have a strong opinion one way or the other. the single-entry-single-exit ideal is more to guide us away from crazy situations like 15 return statements and two more branches that don't return at all! – mendota Aug 18 '16 at 19:15
  • A similar question has also been answered at Stack Overflow: https://stackoverflow.com/questions/36707/should-a-function-have-only-one-return-statement – NMrt Jul 07 '17 at 01:26
  • John Carmack did it all the time in Doom. It's easier to get work done faster. Your tests should prove your work works. not mantras that make more work. Like Goto is evil. https://github.com/id-Software/DOOM/blob/77735c3ff0772609e9c8d29e3ce2ab42ff54d20b/linuxdoom-1.10/p_doors.c#L207 – visc Mar 12 '18 at 15:01
  • Check this out: [Why Many Return Statements Are a Bad Idea in OOP](https://www.yegor256.com/2015/08/18/multiple-return-statements-in-oop.html) – yegor256 Jan 02 '19 at 18:27
  • 8
    That is one of the worst articles I have ever read. It seems like the author spends more time fantasising about the purity of his OOP than actually figuring out how to achieve anything. Expression and evaluation trees have value but not when you can just write a normal function instead. – DeadMG Jan 02 '19 at 22:23
  • 48
    You should remove the condition altogether. The answer is 42. – cambunctious Mar 15 '19 at 16:03
  • 1
    Early returns to simplify a function and readability are usually good, but one responsibility for a function usually leads to more modular and readable code. If you have a condition that is not purely for an early return, chances are higher your function is breaching single responsibility principals (if you care about that concept). – Mythics May 08 '19 at 13:49
  • I'm finding myself fighting against the single return habit in some performance-critical code, where I'm noticing it makes a speed difference in certain hot spots. In some cases, bailing out of the function early is cheaper than having to test a bunch of "ok to proceed" logic. – Emile Cormier Jan 28 '20 at 18:25
  • 1
    I often have to fight this bias too. I'm an advocate of early returns, either for guard clauses, or in cases where the responsibility has been handled (but more cases remain.) I find it leads to much cleaner, simpler, more linear code with fewer bugs. – Jeff Learman Jun 10 '20 at 15:49
  • On the other hand, I've seen cases where optimizers work better on code with no returns and no break statements in loops. – Jeff Learman Jun 10 '20 at 15:50
  • Not really an answer to your question, but personally, I would write that as: `if (cond) { return 42; } return 97;` No need for the `else`. Since there was a return in the `if` block, it's implied that anything after that block is already an `else`. This actually slightly decreases complexity. – Dan Jones Jun 10 '20 at 16:04
  • Surprised no-one has mentioned MISRA-C yet,.I think it used to be rule 17.4 (unsure if that's correct in newer editions). MISRA-C is intended for use in specific environments and often specific industries. The implication being that if you are not in those envs or industries, and do not have to conform to a specific standard and are not coding with particular languages... Then follow common sense. – Toby Jun 10 '20 at 19:55
  • @cambunctious But what is the question? ;) – Caltor Jun 11 '20 at 14:05
  • As another alternative to the first one you could remove the `else` and unindent the second `return`. Some might prefer that style. – Caltor Jun 11 '20 at 14:11
  • @Caltor It was an allusion. See https://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy#Answer_to_the_Ultimate_Question_of_Life,_the_Universe,_and_Everything_(42) – cambunctious Jun 11 '20 at 17:47
  • @cambunctious so was mine! :D Quoting from the wikipedia article you referenced - "In Life, the Universe and Everything, a character named Prak who knows all that is true, confirms that 42 is indeed The Answer, and that it is impossible for both The Answer and The Question to be known in the same universe, as they will cancel each other out and take the Universe with them—to be replaced by something even more bizarre (as described in the first theory) and that it may have already happened (as described in the second)" – Caltor Jun 12 '20 at 15:44
  • @Caltor my bad! – cambunctious Jun 12 '20 at 18:16
  • Related: *[The Forgotten Art of Structured Programming - Kevlin Henney. C++ on Sea 2019](https://www.youtube.com/watch?v=SFv8Wm2HdNM)* – Peter Mortensen Jun 14 '20 at 22:23

14 Answers14

1431

"Single Entry, Single Exit" was written when most programming was done in assembly language, FORTRAN, or COBOL. It has been widely misinterpreted, because modern languages do not support the practices Dijkstra was warning against.

"Single Entry" meant "do not create alternate entry points for functions". In assembly language, of course, it is possible to enter a function at any instruction. FORTRAN supported multiple entries to functions with the ENTRY statement:

      SUBROUTINE S(X, Y)
      R = SQRT(X*X + Y*Y)
C ALTERNATE ENTRY USED WHEN R IS ALREADY KNOWN
      ENTRY S2(R)
      ...
      RETURN
      END

C USAGE
      CALL S(3,4)
C ALTERNATE USAGE
      CALL S2(5)

"Single Exit" meant that a function should only return to one place: the statement immediately following the call. It did not mean that a function should only return from one place. When Structured Programming was written, it was common practice for a function to indicate an error by returning to an alternate location. FORTRAN supported this via "alternate return":

C SUBROUTINE WITH ALTERNATE RETURN.  THE '*' IS A PLACE HOLDER FOR THE ERROR RETURN
      SUBROUTINE QSOLVE(A, B, C, X1, X2, *)
      DISCR = B*B - 4*A*C
C NO SOLUTIONS, RETURN TO ERROR HANDLING LOCATION
      IF DISCR .LT. 0 RETURN 1
      SD = SQRT(DISCR)
      DENOM = 2*A
      X1 = (-B + SD) / DENOM
      X2 = (-B - SD) / DENOM
      RETURN
      END

C USE OF ALTERNATE RETURN
      CALL QSOLVE(1, 0, 1, X1, X2, *99)
C SOLUTION FOUND
      ...
C QSOLVE RETURNS HERE IF NO SOLUTIONS
99    PRINT 'NO SOLUTIONS'

Both these techniques were highly error prone. Use of alternate entries often left some variable uninitialized. Use of alternate returns had all the problems of a GOTO statement, with the additional complication that the branch condition was not adjacent to the branch, but somewhere in the subroutine.

Thanks to Alexey Romanov for finding the original paper. See http://www.cs.utexas.edu/users/EWD/ewd02xx/EWD249.PDF, page 28 (printed page number is 24). Not limited to functions.

kevin cline
  • 33,608
  • 3
  • 71
  • 142
  • 88
    And don't forget *spaghetti code*. It was not unknown for subroutines to exit using a GOTO instead of a return, leaving the function call parameters and return address on the stack. Single exit was promoted as a way to at least funnel all the code paths to a RETURN statement. – TMN Nov 10 '11 at 15:52
  • 7
    @TMN: in the early days, most machines didn't have a hardware stack. Recursion generally wasn't supported. Subroutine arguments and return address were stored in fixed locations adjacent to the subroutine code. Return was just an indirect goto. – kevin cline Nov 10 '11 at 16:33
  • 9
    @kevin: Yeah, but according to you this doesn't even mean anymore what it was invented as. (BTW, I'm actually reasonably sure that Fred asked were the preference for the _current_ interpretation of "Single Exit" comes from.) Also, C has had `const` since before many of the users here were born, so no need for capital constants anymore even in C. But [Java preserved all those bad old C habits](http://chat.stackoverflow.com/transcript/10?m=1834765#1834765). – sbi Nov 10 '11 at 22:08
  • 7
    So do exceptions violate this interpretation of Single Exit? (Or their more primitive cousin, `setjmp/longjmp`?) – Mason Wheeler Nov 22 '11 at 01:13
  • 1
    @Mason: The original article on SESE was focused on provable code. Unrestricted branching creates a combinatorial explosion in possible program states. The use of exceptions could also complicate a correctness proof, if the catch clause accesses local variables that may or may not have been initialized in the main line of code. – kevin cline Nov 22 '11 at 21:15
  • 3
    Even though the op asked about the current interpretation of single return, this answer is the one with the most historical roots. There's no point in using a single return as a **rule**, unless you want your language to match the awesomeness of VB (not .NET). Just remember to use non-short-circuit boolean logic as well. – acelent Nov 29 '12 at 00:44
  • Is that an early form of the sort of continuation passing now seen in promise-oriented programming? – Damian Yerrick Feb 24 '17 at 15:35
  • In addition to Algol, Scheme also supports [continuations](https://en.wikipedia.org/wiki/Continuation), and enables some pretty cool options, if the dev can accept the risks. – Justin R. May 02 '17 at 23:33
  • 1
    This doesn't just apply to assembly. There was also some research done at Microsoft (on C codebases) that found multiple returns contributed to higher bug frequency. See: https://www.amazon.com/Writing-Solid-Code-20th-Anniversary/dp/1570740550/ref=pd_lpo_sbs_14_t_0?_encoding=UTF8&psc=1&refRID=D352JQB8YPXZW4V8HPHC – JivanAmara Dec 05 '17 at 22:12
  • @JivanAmara: Multiple returns from functions that allocate resources can lead to resource leaks. In C memory allocation happens frequently. Multiple returns from functions that allocate memory or other resources should indeed be forbidden. Memory management should be separated from computation. C# and Java have the same problem with non-memory resources. This was somewhat ameliorated by the introduction of the C# using statement and Java's try variables (or Lombok @Cleanup). – kevin cline Dec 06 '17 at 19:51
  • That's nonsense. It has nothing to do with a specific language with the exception perhaps of assembly. A single return gives you a single point of return at the risk of stating the obvious. You can set a single breakpoint there, for example, and you're sure to get there. Now, if you want to have an assertion mechanism or pre-check of arguments at the head, for bad arguments, that preserves your single return point for the normal logic of your method, without cluttering on bad argument checks. – Rick O'Shea Mar 09 '18 at 20:00
  • 4
    @RickO'Shea: How long does it take to set a breakpoint? I'm not willing to complexify the code to avoid having to set multiple breakpoints in an IDE. I keep my functions short enough to fit on the screen, so it's no problem to set a breakpoint on all the returns. – kevin cline Mar 10 '18 at 21:00
  • 3
    @RickO'Shea My IDE lets me put a breakpoint on the final `}` of a function, and breaks there after any of the `return` statements is executed. – Caleth Oct 08 '18 at 12:31
  • 3
    "Single Exit meant that a function should only return _to_ one place" — {{citation needed}}. This answer could be greatly improved by a link to the place Dijkstra (or anyone else) is supposed to have talked about Single Exit with this particular meaning. As it is, it smells like a folk etymology to me. – Quuxplusone May 05 '20 at 15:49
  • 2
    I decided to check and there is something similar in http://www.cs.utexas.edu/users/EWD/ewd02xx/EWD249.PDF, page 28 (printed page number is 24). Not limited to functions. – Alexey Romanov Jun 11 '20 at 10:53
  • Thanks Alexey. I seem to remember reading that paper when it was fairly new. It would have been about 1976. – kevin cline Jun 12 '20 at 09:53
  • 1
    Early return makes 700 lines long methods significantly less readable, except when they consist of one switch statement or if-else chain, with one return per condition, where the early return is hard to miss. Just like being barefoot at home is a horrible idea if you've let you're overflowing sewers spill sewage all over your house for a week because meh ‍♂️ Write short methods with a single responsibility and most code style issues will go away – yeoman Jun 12 '20 at 12:08
  • @yeoman *Early return makes 700 lines long methods significantly less readable* When are "700 lines long methods" **ever** "readable"? When you're complaint is, "That puts a worm into my 700-line pile of poo!", your problem isn't the worm. Nevermind more than implying that early return will always make code "significantly less readable" is, at best, an assertion without evidence that only takes one counterexample to prove false... – Andrew Henle Jan 27 '22 at 19:35
  • 1
    Quote from my comment (actually the last sentence): Write short methods with a single responsibility and most code style issues will go away – yeoman Jan 29 '22 at 07:23
  • 2
    @yeoman early return has made almost every overly long method I've seen *more* readable. Words cannot describe the rage and desire for violence I have learned to feel when I'm done digging through that entire 700 lines someone else left behind (in order to refactor it) only to conclude "oh that's nice, I *could* have known in two lines up-front that if this condition is true then the remaining 698 don't apply, but that would've been too easy. So glad I had to load the entire 700 lines of behavior into my working memory and consciously bookkeep which possibilities just fall through." – mtraceur Feb 10 '23 at 15:25
  • @yeoman of course there are harms of readability which multiple return can cause. Some things *are* clearer when they're not factored as multiple returns. And I do think it's language-relative... For example, in a language like C where manual boilerplate is more commonly necessary for almost everything, multiple return is liable to come with duplication of boilerplate, which ends up being worse for readability than a single return. But I've seen more readability harm caused by bending the logic to a single return shape than by the freedom to choose the number of returns to best suit the logic. – mtraceur Feb 10 '23 at 15:56
  • This answer is great and I wish I had been taught this in school. I ended up eventually independently thinking through what guarantees we didn't have, what was harder, and what coupling/complexity was inherently created by unstructured jumping. And one of the worst parts of unstructured jumping is exactly this: the inability to predict where a function would return to (or which instruction in your code other code might jump to). Calling a function and being able to trust that it will return to your code instead of somewhere else is *huge* for enabling reasoning about code. – mtraceur Feb 10 '23 at 16:28
  • I am in favor of early return :) I'm just not in favor of using it as an excuse for having super long functions Of course, when refactoring a super long functions, early returns can help a LOT, just as you describen, @mtraceur – yeoman Feb 12 '23 at 13:37
1041

This notion of Single Entry, Single Exit (SESE) comes from languages with explicit resource management, like C and assembly. In C, code like this will leak resources:

void f()
{
  resource res = acquire_resource();  // think malloc()
  if( f1(res) )
    return; // leaks res
  f2(res);
  release_resource(res);  // think free()
}

In such languages, you basically have three options:

  • Replicate the cleanup code.
    Ugh. Redundancy is always bad.

  • Use a goto to jump to the cleanup code.
    This requires the cleanup code to be the last thing in the function. (And this is why some argue that goto has its place. And it has indeed – in C.)

  • Introduce a local variable and manipulate control flow through that.
    The disadvantage is that control flow manipulated through syntax (think break, return, if, while) is much easier to follow than control flow manipulated through the state of variables (because those variables have no state when you look at the algorithm).

In assembly it's even weirder, because you can jump to any address in a function when you call that function, which effectively means you have an almost unlimited number of entry points to any function. (Sometimes this is helpful. Such thunks are a common technique for compilers to implement the this pointer adjustment necessary for calling virtual functions in multiple-inheritance scenarios in C++.)

When you have to manage resources manually, exploiting the options of entering or exiting a function anywhere leads to more complex code, and thus to bugs. Therefore, a school of thought appeared that propagated SESE, in order to get cleaner code and less bugs.


However, when a language features exceptions, (almost) any function might be exited prematurely at (almost) any point, so you need to make provisions for premature return anyway. (I think finally is mainly used for that in Java and using (when implementing IDisposable, finally otherwise) in C#; C++ instead employs RAII.) Once you have done this, you cannot fail to clean up after yourself due to an early return statement, so what is probably the strongest argument in favor of SESE has vanished.

That leaves readability. Of course, a 200 LoC function with half a dozen return statements sprinkled randomly over it is not good programming style and does not make for readable code. But such a function wouldn't be easy to understand without those premature returns either.

In languages where resources are not or should not be managed manually, there is little or no value in adhering to the old SESE convention. OTOH, as I have argued above, SESE often makes code more complex. It is a dinosaur that (except for C) does not fit well into most of today's languages. Instead of helping the understandability of code, it hinders it.


Why do Java programmers stick to this? I don't know, but from my (outside) POV, Java took a lot of conventions from C (where they make sense) and applied them to its OO world (where they are useless or outright bad), where it now sticks to them, no matter what the costs. (Like the convention to define all your variables at the beginning of the scope.)

Programmers stick to all kinds of strange notations for irrational reasons. (Deeply nested structural statements – "arrowheads" – were, in languages like Pascal, once seen as beautiful code.) Applying pure logical reasoning to this seems to fail to convince the majority of them to deviate from their established ways. The best way to change such habits is probably to teach them early on to do what's best, not what's conventional. You, being a programming teacher, have it in your hand. :)

sbi
  • 9,992
  • 6
  • 37
  • 56
  • 64
    Right. In Java, cleanup code belongs in `finally` clauses where it gets executed regardless of early `return`s or exceptions. – dan04 Nov 09 '11 at 09:41
  • 18
    @dan04 in Java 7 you don't even need the `finally` most of the time. – R. Martinho Fernandes Nov 09 '11 at 09:45
  • 1
    +1 for the explanation of explicit resource management, I never realized that. Although, I don't agree 100% with your updates. I can easily demonstrate you code with multiple returns (and have often experienced in practice) which degrades readability. – Steven Jeuris Nov 09 '11 at 12:02
  • 113
    @Steven: Of course you can demonstrate that! In fact, you can show convoluted and complex code with any feature that can also be shown to make code simpler and easier to understand. _Everything_ can be abused. ___The point is to write code so that it is easier to understand___, and when that involves throwing SESE out the window, so be it, and damn the old habits that applied to different languages. But I wouldn't hesitate to control execution by variables either if I'd think it made the code easier to read. It's just that I cannot remember having seen such code in almost two decades. – sbi Nov 09 '11 at 12:31
  • @sbi: true, but I have, and that's most likely our 'difference' (which doesn't turn out to be a difference) in opinion. See for example [this Code Review question](http://codereview.stackexchange.com/q/5754/2254). It doesn't involve `return` but `continue`, but the same principle of unreadability due to early returns applies. – Steven Jeuris Nov 09 '11 at 12:35
  • 1
    @sbi: Controlling execution with variables has largely been supplanted with polymorphism (no more 'catch-all' procedures controlled by magic flags). I really only see it in state machines anymore. – TMN Nov 09 '11 at 13:36
  • 4
    Great explanation, just don't neglect that memory isn't the only resource of concern. There are also file handles, database connections, mutexes, etc. that last time I checked Java doesn't clean up as automatically as it does memory, and often needs to be more deterministic than memory garbage collection, i.e. you need to guarantee the resource is released when the function exits, not at some future garbage collection sweep. – Karl Bielefeldt Nov 09 '11 at 13:45
  • 25
    @Karl: Indeed, it is a severe shortcoming of GC languages like Java that they relieve you from having to clean up one resource, but fail with all the others. (C++ solves this problem for all resources using [RAII](http://stackoverflow.com/q/712639/140719).) But I wasn't even talking of only memory (I only put `malloc()` and `free()` into a comment as an example), I was talking about resources in general. I also wasn't implying GC would solve these problems. (I did mention C++, which doesn't have GC out of the box.) From what I understand, in Java `finally` is used to solve this problem. – sbi Nov 09 '11 at 14:15
  • 5
    I know a lot of the really bad habits that my place adheres to is mainly argued for using reading printouts. They argue that you can't see type in a printout, so prefixing with type (a, str, lp), is a good thing. They argue declaring variables at the top of scope is good because you know where to look when you read the printout. I'm wondering why if meetings are all that important, we don't have tablet machines instead of printouts. Maybe I'll suggest that at a later meeting.... over a printout. – Lee Louviere Nov 09 '11 at 15:03
  • @sbi The tangential comments were removed (note to everyone: any criticism that takes a dozen comments to flesh out needs to be its own answer), but can you improve your answer to incorporate the responses to the comment feedback you've received here? Anything important to understanding your answer shouldn't be buried here. –  Nov 09 '11 at 21:36
  • 8
    -1: Sorry, SESE was not initially proposed to prevent resource leaks. In C, the best practice may be to pass a function pointer to a function that allocates the resource, calls the passed function, then releases the resource. – kevin cline Nov 09 '11 at 22:14
  • 1
    In C#, "using" is preferred over finally, to clean up resources that implement IDisposable. – Dave Van den Eynde Nov 10 '11 at 14:55
  • @Dave: Indeed, I should have known that! Will fix. – sbi Nov 10 '11 at 15:10
  • 6
    @kevin: "Single Exit" _is_ used nowadays the way I described it. It might well have started out for other reasons, but SESE advocates will certainly point out that "Single Exit" is necessary for proper resource cleanup. – sbi Nov 10 '11 at 15:14
  • @Xaade Definetely not using the 'var' keyword in C# I guess... – riezebosch Nov 10 '11 at 15:26
  • 4
    Added from personal experience: SESE certainly leads to more complicated code- monsters such as the function that ends with 17 closing braces. Also, if you define the logic of an inner conditional, often there are very bizarre derivations of what is actually being checked from the mathematical sense. For a short function, fine SESE. If it doesn't fit on a screen, don't try SESE. Ultimately it's a style thing, used to be cannon, now seen as bad. 30 years on from now we'll look at a few "best practices" of today and think "that's terrible! what were they thinking?" – anon Nov 11 '11 at 04:00
  • 3
    @anon: SESE being a "cannon" is a wonderful Freudian slip. `:)` Anyway, any function that doesn't fit on one screen is too long. – sbi Nov 11 '11 at 08:52
  • 14
    @sbi: More important for a function (procedure, method, etc.) than being no more than a page long is for the function to have a clearly defined contract; if it's not doing something clear because it's been chopped up to satisfy an arbitrary length constraint, that's Bad. Programming is about playing off different, sometimes conflicting forces against each other. – Donal Fellows Nov 22 '11 at 14:20
  • 2
    "So why do Java programmers stick to this?": I didn't hear this advice coming from Java programmers more often than coming from C++ or C# programmers. – Giorgio Nov 09 '12 at 12:27
  • 6
    " you basically have three options" -- Wrong. I use this: `ftype f() { resource res = acquire_resource(); ftype ret = f_inner(res); release_resource(res); return ret; }` ... then f_inner can do all the returning it wants. – Jim Balter May 10 '13 at 18:50
  • 1
    -1: You can reach "Single Exit" in the example with `void f() { resource res = acquire_resource(); if( !f1(res) ) f2(res); release_resource(res); }`. The logic behind the flow becomes even clearer. I think you should look for a better example. – Piovezan Jan 28 '14 at 12:49
  • This answer has a few _non sequiturs_. I can't see how introducing a local variable to be returned at the function's end necessarily leads to manipulating control flow through it. I can't find any other argument for the statement "SESE often makes code more complex" and can't see how cyclomatic complexity is increased without performing inappropriate control flow. I don't think freeing up resources is the only argument in favor of "single exit" as during maintenance you may need to add code right before the (preferably unique) return point, not necessarily for freeing up resources (you may... – Piovezan Jan 28 '14 at 17:06
  • ...want to call a log function or perform any other function/method call that is necessary in your flow). As a Java programmer, I find SE to be a valid extension to DRY and not a C convention to be blindly followed (defining variables at the beginning of the scope OTOH is yuck). Disliking SE to me suggests a tendency to avoid negative logic in control flow statements as well (`if (!condition)`) which may reflect a weakness in programming logic (negative logic statements should be harder to read, but not that much). – Piovezan Jan 28 '14 at 17:06
  • 6
    @Piovezan: _"I can't see how introducing a local variable to be returned at the function's end necessarily leads to manipulating control flow through it."_ This is such a common case that I never thought I would need to show code doing that. Think a C algorithm, where all functions invoked return an error status. That is stored in a variable `err`, and if one function returns an error, all the others must not be called anymore. As for the rest: I won't even attempt to reply to that. – sbi Jan 31 '14 at 09:40
  • 1
    You still want to do this with C++, because returning a single object lets the compiler avoid copies and moves entirely. – user541686 May 22 '14 at 08:08
  • 4
    @Mehrdad: 1. Write good, easy to understand, well maintainable code. 2. Fix the bugs. 3. Test whether it's fast enough. 3a. If it is, your done. 3b. If it isn't, profile and measure to find the (few) hotspots, and sacrify readability there. Only there. – sbi May 22 '14 at 08:49
  • @sbi: Or you could just learn to do it right from the beginning so that you can structure your code readably without sacrificing the optimization or hindering debugging. I don't think I've ever seen the single-return convention harm code in some way, but I've definitely seen multiple returns rear their ugly heads when I'm trying to set a breakpoint to find what values a function is returning and inevitably end up missing one of the 4 returns in the function. – user541686 May 22 '14 at 09:34
  • 4
    @Mehrdad: I have answered to this [years ago](http://programmers.stackexchange.com/questions/118703/where-did-the-notion-of-one-return-only-come-from/118717?noredirect=1#comment217541_118717). – sbi May 22 '14 at 15:40
  • @DonalFellows yes, but it is almost always possible to reduce logic into composable atoms of clear meaningful behavior that are short enough to fit even within 16 lines, let alone a modern screen. – mtraceur Feb 10 '23 at 19:30
98

On the one hand, single return statements make logging easier, as well as forms of debugging that rely on logging. I remember plenty of times I had to reduce the function into single return just to print out the return value at a single point.

  int function() {
     if (bidi) { print("return 1"); return 1; }
     for (int i = 0; i < n; i++) {
       if (vidi) { print("return 2"); return 2;}
     }
     print("return 3");
     return 3;
  }

On the other hand, you could refactor this into function() that calls _function() and logs the result.

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
perreal
  • 341
  • 2
  • 8
  • +1 good point. Printing debugging is useful to me and this is good idea for it. – user712092 Nov 09 '11 at 15:45
  • 44
    I would also add that it makes debugging easier because you only ever need to set one breakpoint to catch all exits* from the function. I beleive that some IDEs let you put a breakpoint on the close brace of the function to do the same thing. (* unless you call exit) – Skizz Nov 10 '11 at 11:03
  • 6
    For a similar reason, it also makes it easier to extend (add to) the function, since your new functionality doesn't have to be inserted before each return. Say you needed to update a log with the result of the function call, for example. – JeffSahol Nov 10 '11 at 16:43
  • 85
    Honestly, if I were maintaining that code, I'd rather have a sensibly-defined `_function()`, with `return`s at appropriate places, and a wrapper named `function()` that handles extraneous logging, than have a single `function()` with contorted logic to make all returns fit into a single exit-point just so I can insert an additional statement before that point. – ruakh Nov 13 '11 at 19:41
  • 14
    In some debuggers (MSVS) you can put breakpoint on last closing brace – Abyx Nov 21 '11 at 21:56
  • 11
    printing != debugging. That's not argument at all. – Piotr Perak Dec 14 '13 at 09:08
  • 2
    @Peri Thank you. Printing *is* a primitive way of debugging but this is just ridiculous. If I had the reputation on this site, I'd love to downvote this answer. – spaaarky21 Nov 26 '14 at 20:16
  • @spaaarky21, check this out: http://blog.awilkins.id.au/2011/10/c-source-to-source-translation.html – perreal Jan 13 '15 at 16:13
  • @JeffSahol Well you may never have a new functionality.. And refactoring should not be difficult if new functionality actually is implemented.. – Koray Tugay Dec 18 '15 at 19:30
  • 1
    You *can't* just refactor into `_function` though. (cc @nakh) How would you log intermediate variables if you needed to? You'd have to return a tuple or struct or something that returns all of them which would mean repeating their names several times. The nice thing about a single return is that it avoids code repetition, lets you identify where to insert extra code later, and gives you access to all the local variables you want to inspect/output, especially when debugging. – user541686 Jun 12 '17 at 13:45
  • 1
    Wouldn't it be better to avoid using the possibly illegal identifier of `_function` for our 'inner function'? https://stackoverflow.com/a/228797/ – Max Barraclough Jun 25 '18 at 09:42
  • 1
    In this case, you should use a `decorator` like @log_debug – Tinmarino Jan 01 '20 at 01:32
  • Call it function_ instead of borderline illegal _function. – gnasher729 Jun 10 '20 at 14:58
61

"Single Entry, Single Exit" originated with the Structured Programming revolution of the early 1970s, which was kicked off by Edsger W. Dijkstra's letter to the Editor, GOTO Statement Considered Harmful. The concepts behind structured programming were laid out in detail in the classic book Structured Programming by Ole Johan-Dahl, Edsger W. Dijkstra, and Charles Anthony Richard Hoare.

"GOTO Statement Considered Harmful" is required reading, even today. "Structured Programming" is dated, but still very, very rewarding, and should be at the top of any developer's "Must Read" list, far above anything from e.g. Steve McConnell. (Dahl's section lays out the basics of classes in Simula 67, which are the technical foundation for classes in C++ and all of object-oriented programming.)

Basil Bourque
  • 1,000
  • 5
  • 10
John R. Strohm
  • 18,043
  • 5
  • 46
  • 56
  • 10
    The article was written in days before C when GOTO's were used heavily. They aren't the enemy, but this answer is definitely correct. A return statement thats not at the end of a function is effectively a goto. – user606723 Nov 09 '11 at 15:21
  • 4
    @user606723 No. In most architectures, the code to return from a function is as simple as writing `leave; ret` (x86) or `pop 1,0` (MMIX). In most cases, that is much simpler than jumping to the exit point. – FUZxxl Nov 09 '11 at 15:34
  • 44
    The article was also written in the days when `goto` could literally go *anywhere*, like right into some random point in another function, bypassing any notion of procedures, functions, a call stack, etc. No sane language permits that these days with a straight `goto`. C's `setjmp`/`longjmp` is the only semi-exceptional case i'm aware of, and even that requires cooperation from both ends. (Semi-ironic that i used the word "exceptional" there, though, considering that exceptions do almost the same thing...) Basically, the article discourages a practice that's long dead. – cHao Nov 09 '11 at 15:41
  • 3
    @cHao: What Dijkstra observed was that GOTOs tended to proliferate ("multiply like cockroaches" might be a better analogy) in code, and multiple GOTOs invariably obscured the intent of the code. This was before the "structured" control structures were common, and before Bohm & Jacopini's [Google is your FRIEND!] formal proof that the structured control structures were all you needed. Today, heavy use of "break", "continue", and "return" in the middle of a random mass of code are just as effective at obfuscating the code's intent as GOTO was in the early days. – John R. Strohm Nov 09 '11 at 16:17
  • 6
    Heavy use of break, continue, return and goto can obfuscate. But they have their place, and when used decently (ie: when they are appropriate), they *increase* readability by pruning the decision tree. The argument was against the unbridled use of goto, not against its use ever -- break/continue, being restricted, are outside of the argument's scope. But even Dijksta would have to admit that abolishing goto entirely is unrealistic -- and Wirth (who published and even *named* that article, and took it to heart) *did* admit as much when he added a goto to pretty much every language he designed. – cHao Nov 09 '11 at 17:50
  • 6
    From the last paragraph of "Goto Statement considered harmful": "in [2] Guiseppe Jacopini seems to have proved the (logical) superfluousness of the go to statement. *The exercise to translate an arbitrary flow diagram* more or less mechanically into a jump-less one, *however, is not to be recommended*. Then *the resulting flow diagram cannot be expected to be more transparent than the original one.*" – hugomg Nov 09 '11 at 18:22
  • 11
    What does this have to do with the question? Yes, Dijkstra's work eventually led to SESE languages, and so what? So did Babbage's work. And perhaps you should re-read the paper if you think it says **anything** about having multiple exit points in a function. Because it doesn't. – jalf Nov 09 '11 at 18:35
  • 4
    @JohnR.Strohm - if the argument is that writing `return` twice in one function will *cause* it to multiply across the code base then I think it's flawed. Just because you wrote it once in one function doesn't mean you'll unwilling have to write it a second time, in another function. That is unless *the construct proved usefully expressive enough as to make it desirable*, but "usefully expressive" is a whole world apart from "parasitic infestation". If the code base is so tightly coupled that it does cause this then I'd start to worry about the elephant in the room first. – Flexo Nov 09 '11 at 18:36
  • @missingno: Thank you for the correction. It has been many years since I read the paper, and I was not aware that Bohm & Jacopini's paper actually preceded it. – John R. Strohm Nov 09 '11 at 19:58
  • From the original question: "Where did this notion of "one return only" come from, and why do people adhere to it rigidly?" In other words, the original question is "Where did Single Entry/Single Exit come from?", and the answer is that it came from the Structured Programming movement of the early 1970s. – John R. Strohm Nov 09 '11 at 20:33
  • 13
    @John, you seem to be trying to answer the question without actually answering it. It's a fine reading list, but you've neither quoted nor paraphrased anything to justify your claim that this essay and book have anything to say about the asker's concern. Indeed, outside of comments you've said *nothing* substantial about the question whatsoever. Consider expanding this answer. – Shog9 Nov 09 '11 at 22:05
  • I find it curious how coroutines (and generators) are making something of a comeback these past few years, as they definitely have the ability to violate part of SESE, albeit in a limited sense. – Donal Fellows Nov 11 '11 at 23:54
  • @DonalFellows: *Part*? :) True coroutines would seem to pretty much turn SESE on its ear. Generators, not quite so much, as they don't choose where to "yield" to; although on a conceptual level there's still a number of "entry points", most languages that use generators aren't able to actually jump straight to those entry points. Generators in those languages are really just hidden state machines. – cHao Oct 26 '12 at 15:24
  • @DonalFellows just five years after your comment, people started to remember structured programming and apply those same ideas to concurrency just as we applied it to jumps back in the day. See ["Structured Concurrency"](http://250bpm.com/blog:71) by Sustrik (main ZeroMQ author, who also wrote libdill as maybe the first structured concurrency implementation) and ["Go Statement Considered Harmful"](https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/) by Smith (main author of the Python library Trio and Python core contributor if I remember right). – mtraceur Feb 10 '23 at 19:47
  • @cHao yep, though the essence of SESE is not the number of entry points and exit destinations - it's if execution is forced to follow structure we can easily reason about and see from the source. A `yield` or `await` is still fine because when you call/resume a generator/coroutine at a spot, the yield/resolve/exception will come back to that spot if it comes back at all. Concurrency's big structured programming slip up was allowing functions to return before all their concurrent branches are done or cancelled. (Which is like a `malloc` without a `free` and like a `longjmp` without a `setjmp`.) – mtraceur Feb 10 '23 at 20:48
  • @mtraceur: The essence of SESE is right there in the name. Sequential routines have a defined entry point and exit point, and you're not to bypass either. It's *so that* the structure is easier to reason about, and is an obvious way to make it so, but is not the only way, as evidenced by the idea of guard clauses, generators, coroutines, etc being a thing. Concurrency's big slip-up was initially preferring explicit threading and then providing ways to claw back control, rather than the more limited async model of subordinate tasks promising to return a value eventually. – cHao Feb 13 '23 at 14:47
  • @cHao If by "defined exit point" you mean defined exit *destination*, sure. Because for example an early-return guard clause doesn't violate SESE *in any way that matters to the goals that SESE's existence is entirely predicated on*. So even if you argue that the essence of a purpose-serving rule is the rule implied by the words of its memorable name rather than its purpose, the "single exit" was always more about a single invocation having just one possible return/exit jump target. – mtraceur Feb 13 '23 at 15:41
  • @mtraceur: A guard clause does, in fact, violate most interpretations of SESE. The goals are irrelevant; the principle isn't defined by the goal, but by the specific way of accomplishing that goal. – cHao Feb 13 '23 at 15:50
  • @cHao I think the only time it's good to value a rule over its purpose is if you want to build a world that favors obstructionist bureaucrats and letter-of-the-rule abusers, so I don't know why this is a hill you want to fight for. Anyway, re: "most interpretations of SESE" - perhaps a glance at the top voted answer on this very question is in order. – mtraceur Feb 13 '23 at 15:53
  • @cHao Re: other point: everything bad about explicit threading with afterthought control clawbacks just boils down to what I said. The ability to start a process/thread/coroutine/task explicitly rather than implicitly is largely irrelevant, but the thing that's good about concurrent execution being *subordinate* and under *control* of the caller is precisely that it allows you to call a function and trust that you don't need to know if it internally uses concurrency. Also (we both forgot to mention) structured preemption: yielding to preemption only at designated visible-in-the-code spots. – mtraceur Feb 13 '23 at 15:54
  • @mtraceur: I think the only time it's good to confuse a rule with its purpose is if you want to check a box to satisfy obstructionist bureaucrats. SESE in most languages these days is a stupid rule and should not be adhered to without a damn good reason, and the top answer basically echoes that assessment. – cHao Feb 13 '23 at 15:58
  • @mtraceur: I didn't mention structured preemption because there's no such thing. That's just cooperative multitasking. The very idea of preemption is that a process has little to no control over when control gets snatched away from it, which is the opposite of "structured" from the process's POV. – cHao Feb 13 '23 at 16:11
  • @cHao If you are unable to know concept exists without someone having already named it and spread it through the culture before you encountered it, I'm sorry but that's a bigger problem I don't have the time or motivation to help with. Cooperative multitasking *is* part of what I call structured preemption. Or to be more precise, cooperative multitasking is the absence of any *preemption*, but if we set out to structure preemption as we did jumps and concurrency, the result ends up looking a lot like cooperative multitasking up to a point. Anyway I'm done here. – mtraceur Feb 13 '23 at 17:15
  • @mtraceur: You can't "structure" preemption, not using a standard definition for both of those words. It's not just that the concept is unknown, it's that logically, it's like having a rectangular circle. One of those definitions has to give. – cHao Feb 13 '23 at 18:51
53

It's always easy to link Fowler.

One of the main examples that go against SESE are guard clauses:

Replace Nested Conditional with Guard Clauses

Use Guard Clauses for all the special cases

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
return result;
};  

                                                                                                         http://www.refactoring.com/catalog/arrow.gif

double getPayAmount() {
    if (_isDead) return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired) return retiredAmount();
    return normalPayAmount();
};  

For more information see page 250 of Refactoring...

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
Pieter B
  • 12,867
  • 1
  • 40
  • 65
  • If your conditional check decides which code path to execute, yes. For simple assignments, no. – DanMan May 24 '14 at 11:16
  • 30
    Another bad example: it could just as easily be fixed with else-ifs. – Jacklynn Jul 06 '15 at 17:58
  • 1
    Your example is not fair, how about this: double getPayAmount() { double ret = normalPayAmount(); if (_isDead) ret = deadAmount(); if (_isSeparated) ret = separatedAmount(); if (_isRetired) ret = retiredAmount(); return ret; }; – Charbel Apr 22 '16 at 06:55
  • +1 For citing an specific Martin Fowler refactoring. I am working and refactoring with code from someone who followed the one return path and it is hell but can improve fast with this refactor. – Borjab Nov 24 '16 at 12:11
  • 9
    @Charbel That's not the same thing. If `_isSeparated` and `_isRetired` can both be true (and why wouldn't that be possible?) you return the wrong amount. – hvd Jan 22 '17 at 09:02
  • It may seem to be a trivial thing nowadays, but nested conditionals will provide a better execution time than guard clauses. Moreover, in many cases an even better execution time can be found by converting the logic into a single-typed switch. IIRC modern compilation / static analysis will generate optimal branch switches. Of course modern coders are so used to fast processors that such issues seem to be trivial. It does indeed depend upon the context - but sometimes we need to count clock cycles. –  Apr 18 '17 at 07:09
  • 9
    @Konchog "_nested conditionals will provide a better execution time than guard clauses_" This **majorly** needs a citation. I have my doubts that it's reliably true at all. In this case, for example, how are early returns any different from logical short-circuiting in terms of generated code? Even if it mattered, I can't imagine a case where the difference would be more than an infinitesimal sliver. So you're applying premature optimisation by making the code less readable, just to satisfy some unproven theoretical point about what you think leads to slightly faster code. We don't do that here – underscore_d Sep 23 '18 at 16:09
  • 1
    @underscore_d, you are right. it depends a lot on the compiler, but it can take more space.. Look at the two pseudo-assemblies and it's easy to see why guard clauses come from high level languages. "A" test(1); branch_fail end; test(2); branch_fail end; test(3); branch_fail end; {CODE} end: return; "B" test(1); branch_good next1; return; next1: test(2); branch_good next2; return; next2: test(3); branch_good next3; return; next3: {CODE} return; –  Sep 24 '18 at 11:32
  • Also the nested conditional in the answer given is naive. Although the ternary has some bad press, the SESE function could be elegantly written as follows (with a newline before each colon) return _isDead ? deadAmount() : _isSeparated ? separatedAmount() : _isRetired ? retiredAmount() : normalPayAmount(); –  Sep 24 '18 at 12:05
  • Multiple return statements can be a **major** PITA when using a breakpoint debugger. For languages that support it, `goto end` with an `end:` label before the return and/or any cleanup code is a better way. – MikeSchinkel Oct 24 '22 at 20:38
22

I wrote a blog post on this topic a while back.

The bottom line is that this rule comes from the age of languages that don't have garbage collection or exception handling. There is no formal study that shows that this rule leads to better code in modern languages. Feel free to ignore it whenever this will lead to shorter or more readable code. The Java guys insisting on this are blindly and unquestioning following a outdated, pointless rule.

This question has also been asked on Stackoverflow

Anthony
  • 221
  • 2
  • 6
  • 12
  • Hey, I can't reach that link anymore. Do you happen to have a version that's hosted somewhere still accessible? – Nic Jan 22 '17 at 04:12
  • Hi, QPT, good spot. I have brought the blog post back and updated the URL above. It should link now! – Anthony Jan 28 '17 at 18:23
  • There's more to it than that though. It's much easier to manage precise execution timing by using SESE. Nested conditionals can often be refactored out with a switch anyway. It's not just about whether or not there's a return value. –  Apr 18 '17 at 07:15
  • If you're going to claim there's no formal study in support of it, it would behoove you to link to one that goes against it. – user541686 Jun 12 '17 at 13:41
  • 3
    Mehrdad, If there is a formal study in support of it, show it. That's all. Insisting on evidence against is shifting the burden of proof. – Anthony Jun 13 '17 at 08:10
  • @Anthony, that is not shifting the burden. The answer states "there is no..." That is a positive claim, which has not been substantiated. The irony is that the answer _itself_ shifts the burden of proof by using the argument from ignorance fallacy, which is essentially: "SESE is bad because nobody can prove it's good with a formal study." – SO_fix_the_vote_sorting_bug Aug 27 '20 at 12:35
  • 5
    Come off it, questioning the statement "There is no formal study that shows this" is a textbook case of asking for "proving a negative". A statement "there is no x" is a textbook "negative claim" which "asserts the non-existence or exclusion of something" so you're wrong about that. Also yes, pointless rules are bad when they cause extra work and lead to awkward code. If there's a good reason for it, fine. But there is not, unless you know something that we don't? – Anthony Sep 03 '20 at 13:15
  • @Anthony — I would have commented on your post but it doesn't accept comments. Anyway, for languages that support it, goto end with an end: label before the return and/or any cleanup code is a better way than multiple returns. – MikeSchinkel Oct 24 '22 at 20:40
14

One return makes refactoring easier. Try to perform "extract method" to the inner body of a for loop that contains a return, break or continue. This will fail as you have broken your control flow.

The point is: I guess that nobody is pretending to write perfect code. So code is reguarly under refactoring to be "improved" and extended. So my goal would be to keep my code as refactoring friendly as possible.

Often I face the problem that I have to reformulate functions completely if they contain control flow breakers and if I want to add only little functionality. This is very error prone as you change whole the control flow instead of introducing new paths to isolated nestings. If you have only one single return at the end or if you use guards to exit a loop you of course have more nesting and more code. But you gain compiler and IDE supported refactoring capabilities.

oopexpert
  • 769
  • 4
  • 7
  • The same applies to variables. Which are the alternative to using control-flow-constructs like early return. – Deduplicator Oct 16 '17 at 22:07
  • 1
    Variables will mostly not hinder you to break your code into pieces in a way that the existing control flow is preserved. Try "extract method". The IDEs are only able to perform control flow preserivng refactorings as they are not able to derive semantics from what you have written. – oopexpert Oct 17 '17 at 07:12
  • Agreed strongly with your last paragraph. I think the current popular answers here are reacting too strongly against the notion of an "outdated law", and this is causing many to skip over the entire analysis of whether it is still considered good _style_ unless there is a counter-prevailing reason not to follow it. I still set up simple methods with a single variable for return tracking in anticipation that a developer will refactor at someday in the future (even if it's not a good idea to refactor!) in order to discourage the possible temptation of putting returns in complex control code. – Jon Aug 04 '23 at 15:28
8

Cyclomatic Complexity

I've seen SonarCube use multiple return statement for determining cyclomatic complexity. So more the return statements, the higher the cyclomatic complexity

Return Type Change

Multiple returns mean we need to change at multiple places in the function when we decide to change our return type.

Multiple Exit

It's harder to debug since the logic needs to be carefully studied in conjunction with the conditional statements to understand what caused the returned value.

Refactored Solution

The solution to multiple return statements is to replace them with polymorphism have a single return after resolving the required implementation object.

Sorter
  • 373
  • 2
  • 4
  • 12
  • 12
    Moving from multiple returns to setting the return value in multiple places doesn't eliminate cyclomatic complexity, it only unifies the exit location. All the problems which cyclomatic complexity can indicate in the given context remain. "It's harder to debug since the logic needs to be carefully studied in conjunction with the conditional statements to understand what caused the returned value" Again, the logic doesn't change by unifying the return. If you have to carefully study code to understand how it works, it needs to be refactored, full stop. – WillD Mar 29 '19 at 18:37
7

Consider the fact that multiple return statements are equivalent to having GOTO's to a single return statement. This is the same case with break statements. As thus, some, like me, consider them GOTO's for all intents and purposes.

However, I don't consider these types of GOTO's harmful and will not hesitate to use an actual GOTO in my code if I find a good reason for it.

My general rule is that GOTO's are for flow control only. They should never be used for any looping, and you should never GOTO 'upwards' or 'backwards'. (which is how breaks/returns work)

As others have mentioned, the following is a must read GOTO Statement Considered Harmful
However, keep in mind that this was written in 1970 when GOTO's were way overused. Not every GOTO is harmful and I would not discourage their use as long as you don't use them instead of normal constructs, but rather in the odd case that using normal constructs would be highly inconvenient.

I find that using them in error cases where you need to escape an area because of a failure that should never occur in normal cases useful at times. But you should also consider putting this code into a separate function so that you can just return early instead of using a GOTO... but sometimes that's also inconvenient.

user606723
  • 1,169
  • 9
  • 13
  • 9
    All structured constructs that replace gotos are implemented in terms of goto. E.g. loops, "if" and "case". This does not make them bad - in fact the opposite. Also, it is "intents and purposes". – Anthony Nov 10 '11 at 15:05
  • Touche, but this doesn't differ my point... It just makes my explanation slightly wrong. oh well. – user606723 Nov 10 '11 at 16:33
  • 1
    GOTO should be always okay as long as (1) target is within the same method or function and (2) the direction is forward in the code (skip some code) and (3) the target is not inside some another nested structure (e.g. GOTO from the middle of if-case to the middle of else-case). If you follow these rules, all misuses of GOTO have a really strong code smell both visually and logically. – Mikko Rantalainen Dec 11 '12 at 11:29
  • Totally agree that multiple returns are like a non-harmful GOTO to a single return. So as that is the case, at least for languages that support GOTO, I say just use GOTOs to a single return instead of using multiple returns. I have been doing that for several years now and the benefits over early return are numerous. – MikeSchinkel Oct 24 '22 at 20:45
4

Is there a historical reason why this convention came about?

The answer I learnt - at university, in the early 1980's - was that single entry and exit were pre-conditions to mathematical proof of correctness of code. This was deemed sufficiently important that it was part of the course.

This is a somewhat more recent (2004) writeup from Cornell in the United States: https://www.cs.cornell.edu/courses/cs312/2004fa/lectures/lecture9.htm

Language, then at least, had little or no impact on this. It was far more about your ability to assert what was, or wasn't true.

Murph
  • 7,813
  • 1
  • 28
  • 41
  • 2
    I think I remember SIGPLAN articles from ACM back in the late 70's early 80's about correctness proving systems that said single return made the job much easier (along with other adornments such as assertions.) Of course, we know what resulted from this effort: we learned a lot about language design but didn't end up with practical tools. I also remember the fights between the provers and the testers in those articles. We know who won that argument. – Jeff Learman Jun 10 '20 at 15:45
2

Multiple returns introduce implicit paths in the code to just past the end of the function (or method) that are visible neither at the individual return statements, nor at the end of the function. This can cause a reviewer to misinterpret the behaviour of the code through no fault of their own. Not all functions can be inspected at a glance and having to check for additional return paths increases the possibility of error.

Where the notation is used as a form of Guard clause advocated in, among others Smalltalk, they are a form of design by contract pre-conditions. In such a case, they are only syntactically ill advised as they do not change the behaviour of the function. Each pre-condition implements a veto on continued execution and their ordering is unimportant.

In the cases attributed to Fowler, this approach is less advisable. Where guards are deterministically evaluated in order to select a behaviour, the returns make the code look like preconditions, but are not and depend on their ordering. This introduces the same state dependencies that the OP identifies as a concern with the added variable. A nested if/else statement would make the classification of cases explicit and simplify understanding.

However, where any one the true guards is nondeterministic evaluated, the notation again becomes one where all paths are equivalent and the multiple returns again become syntactic baggage that other notations could remove. This is equivalent to Horn clauses and provides a safe model for discriminating between multiple paths in a program.

Correct code outweighs pretty code in all cases.

Pekka
  • 161
  • 4
2

With only one exit point from a function you can easily add postconditions to make sure the result has the desired properties; here is an example in Oberon (which does not support early return):

PROCEDURE P(n: INTEGER): INTEGER;
    VAR result: INTEGER;
BEGIN
    ASSERT(n >= 0);
    result := 0;
    ...
    (*complicated logic*)
    ...
    ASSERT(result >= 0);
    ASSERT(result MOD 2 = 0)
RETURN result
END P

With early returns you would have to add the postconditions before each return statement which makes the function hard to read and maintain.

See also https://en.wikipedia.org/wiki/Postcondition

0

One place this may be coming from not covered by any of the existing answers is functional programming. Many functional languages enforce single-exit at the language level: for instance, apparently OCaml, Clojure, Erlang and F# do not even have a return statement, while Haskell’s return is a function, not a control flow operator, and doesn’t allow multiple exit. Scala, which also supports a more imperative style, has a return statement which does affect control flow... but best practice on the functional side of things is to never, ever use it so we’re right back at single exit.

So why are multiple returns a bad thing in functional programming?

Return as an expression

Functional languages are expression-oriented and generally want everything that can to be an expression, meaning that it evaluates to some value. (Which, incidentally, is how they can manage not having return statements at all - the function body is an expression, so has a value, and then the function evaluates to that value. No explicit return needed.)

Functional languages also want their expressions to be referentially transparent. What this means is that you can swap out the expression for its value without changing the behaviour of the program. This means that you can extract parts of methods, inline them, save intermediate results in variables, etc. etc. and know that this refactoring cannot possibly break anything.

But if we try to treat return as an expression, things start getting weird. Looking at Scala as an example because it's what I'm most familiar with and it is a functional language allowing returns...

def f1 : Int = {
   if (condition)
      return 42
   97
}

// should have the same behaviour under referential transparency
// but in practice f2 will always evaluate to 97
def extractedReturn : Int = return 42

def f2: Int = {
  if (condition)
    extractedReturn
  97
}

// wait, what happens if we lazy-evaluate the return expression outside the method it's in?
def lazyReturn : () => Int = () => return () => 42

def f3: Int = {
  if (condition)
    lazyReturn()
  97
}

(Examples inspired from tpolecat's post linked above; the last one actually throws an exception.)

The problem is that an explicit return has side effects - it doesn't just evaluate to a value, it also changes the control flow of the program. And that causes lack of referential transparency, as well as the weird edge cases involving lazy execution.

You could argue that return should be treated as a special case and not held to the functional standards, similar to how some functional languages support throwing exceptions. This is the path Scala took. But there's still a real cost to actually using the return statement, because it's so strongly against the language paradigm. And there's not much benefit, because...

Alternate functional idioms

For pretty much all of the use cases where multiple return statements are useful in more imperative code, there are functional idioms that handle the same situation without needing an early exit point.

Take the if/else example in the question. In functional languages, if isn't a control flow statement, it's an expression with a value, behaving pretty much exactly like the ternary operator:

def singleExitIf(condition: Boolean) : Int = 
  if (condition)
    42
  else
    97

More complex if/else if/else if/... chains can often be handled well by pattern matching, which functional languages typically have strong support for.

Guard clauses prior to some lengthy computation are a natural fit for monads. In the functional paradigm you're generally either already working in or can easily lift yourself into the context of some monad which has something like a "fail-fast" execution path (the None type in Maybe/Option, Left in right-biased Either, errors in IO, etc.) So if you want to abort computation early subject to some condition, instead of returning early you switch into that path:

def doComplexCalculation(person: Person) : Int =
  Some(person)
    .filter(_.isAlive) // if the person is dead this will evaluate to None
    .map { livingPerson =>
       // long complex calculations requiring the person to be alive go here
    }
    .getOrElse(deadPersonFallbackValue)

How do you break out of a loop early? Well, you almost certainly don't want to be using a loop in the first place. Maybe you want some collection-level operation - some of these terminate traversal early based on a condition, like find or takeWhile. Or maybe you need a tail recursive function where you can stop the recursion at the right point.

In short, if you think you need an early return in a functional language, there's almost invariably some more idiomatic way to express it that doesn't require multiple exit points.

Conclusion

In functional languages, early returns violate the functional paradigm and also don't offer the same benefits they do in more imperative/OOP ones. Most functional languages don't allow them at all, and in the one that does they're viewed as bad practice. Programmers coming from the functional side of things may extend that to more imperative/OOP languages - especially because once you're used to multiple exit points not being a possibility, code with early returns becomes significantly harder to read and understand and it's easy to conclude they must be bad style.

Astrid
  • 176
  • 3
  • I'd welcome input from people more familiar with other functional languages; I tried to keep this general (and it's very striking that *no* other functional language I checked allows early return) but my experience is with Scala and this answer may be biased in that direction. I also couldn't actually find explanations for why functional programming and explicit return don't mix and ended up cobbling one together from tpolecat's post and my own experience, so addenda and corrections are very welcome. – Astrid Jul 15 '22 at 22:45
-1

Today, there is one practical reason: To make debugging easier. If you want to see which value a function returns, it is often easiest to store the result in a variable which is returned at the very end, so you can set a single breakpoint and check the return value. Or print it for debugging. If you have a debugger that can do better, good for you. Would be perfect to have a debugger that lets you set a breakpoint at the closing bracket of a function, but actually breaks at the return statement that’s returning and shows the value.

gnasher729
  • 42,090
  • 4
  • 59
  • 119
  • 2
    Set a breakpoint at the point of origin, where the function was called, and examine the return value there. – Robert Harvey Jun 10 '20 at 16:51
  • Robert, I would have expected better from you. f(self.prop1, self.prop2, self.prop3). Now set a breakpoint to get the return value of prop2. – gnasher729 Jun 11 '20 at 13:48
  • 4
    It's not a good enough reason to upend your entire coding style. Early exit is a very useful technique; it cleans up endless if-else ladders, and greatly simplifies the logic. – Robert Harvey Jun 11 '20 at 13:53
  • Up to everyone to decide themselves. Nevertheless, your first comment was nonsense. – gnasher729 Jun 11 '20 at 13:56
  • 2
    A good debugger should allow you to breakpoint all returns at once. If your debugger doesn't, consider contributing to improve it (unless it's closed-source, in which case you should probably switch). – Toby Speight Jun 11 '21 at 09:41