1352

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,954

14 Answers14

1454

"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,798
1049

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
  • 10,052
99

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
  • 9,209
perreal
  • 341
63

"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.)

56

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
  • 9,209
Pieter B
  • 13,310
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
  • 231
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
  • 779
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
  • 393
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,170
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,843
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
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
-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
  • 49,096