Why should I only use a "Return" in each function?

Asked

Viewed 6,835 times

152

It is common to see the recommendation to use only one return by function/method. But this seems somewhat meaningless and leaves the code more confusing in many cases.

See the examples:

The way I usually do:

if (cond)
    return 0;
else
    return 1;

How it is recommended:

int resultado;
if (cond)
    resultado = 0;
else
    resultado = 1;
return resultado;

Why is there such a recommendation? What is gained in using a single return?

  • 1

    I sometimes consider it better to have only one Return because you can easily debug the result of the method. By checking what comes out in resultado in this case (because if not, you will need to place two or more inspections, one for each Return, to see where the method is entering). In compensation this sometimes increases the cyclomatic complexity also.

4 answers

183


Should not, do what is best for the readability of the code

Basically this rule is called Single Entry, Single Exit (SESE) and is an important recommendation in languages that use explicit (or manual) memory management. Especially in languages that do not rely on error handling by exceptions.

It is important to realize that the use of SESE almost always makes code more complex.

In languages like C, for example, when you need to explicitly release resources allocated at the end of the function execution, there is a need to replicate the release of these resources before each return and there is a risk of confusion when there are several of them. If there is any confusion, the code of the function ends up executing it without making the release or doing improperly.

Resource management and exceptions

In languages that make use of exceptions, SESE already makes even less sense since any function called can potentially generate an output from the currently running function. Even if you can make use of documentation to know if a used function can issue an exception by ending the current function, you have at hand a difficulty understanding the future maintenance code.

In C++, for example, where exceptions exist, it is fundamental to use automatic or semi-automatic resource management through the so-called technique RAII (Resource Acquisition Is Initialization). In Java and C# there are constructions like try-finally and using to deal with resource release.

Since the program can have several implicit exit points, by releasing exceptions, where you do not even know where it comes from, it is no use trying to unify the explicit output in a single point. You will create a confusing stream in the code and will not solve any problem.

In languages with exception treatment you have two situations:

  • the existence of resources that facilitate the call "clean out" releasing all resources whatever happens;
  • the ineffectiveness of the technique since there can always be several invisible exit points in any function called that emits a throw or raise.

Then the SESE technique became unnecessary and even not recommended since in many cases the program flow ends up being complicated in the name of the reliability of the single output.

Unfortunately there are still those who find it important in any language and disseminate information that is partially obsolete, as occurs in many other cases of "good practices" propagated.

To code readability and ease of maintenance are most important a guide that brings no benefit in languages that have their own tools for managing and releasing resources. It is common to have cyclomatic complexity reduced when using several return making the execution of the function end as soon as possible.

It is evident that make proper use of the return and maintain a coherent logic is still necessary no matter how often it appears in the body of the function.

Legitimate use

But let’s look at a case where the use of SESE would be legitimate and would solve an existing problem, as is common in language C:

void funcao1() {
  resource recurso = acquire_resource();  //talvez possa ser um malloc() aqui
  if( funcao2(recurso) )
    return; // esqueceu do código de liberação antes do return e o recurso vazou
  .
  .
  .
  funcao3(recurso);
  release_resource(recurso);  //poderia ser um free()
  return;
}

With SESE:

void funcao1() {
  resource recurso = acquire_resource();  //talvez possa ser um malloc() aqui
  if( funcao2(recurso) )
    goto saida; // manda para o ponto onde o recurso é liberado e termina a função
  .
  .
  .
  funcao3(recurso);
saida:
  release_resource(recurso);  //poderia ser um free()
  return;
}

In languages so you have some options:

  • Repeat the resource release code

    Horrific solution and facilitates the creation of errors, hurts the DRY

  • Put a goto for the excerpt of the code that makes the release of resources

    The release code must be the last part of the function and is one of the best uses for the goto

  • Create a local variable to help program flow control

    It is far more complicated to control the flow this way than using language controls that hold the state for flow control implicitly.

As these languages bring about these difficulties, the SESE technique was created that requires there to be only one exit point, that is to say a single return.

It is much easier to standardize this technique. If the programmer has in his head that he should always do this, as if obliged by the language, the chance of him forgetting to make the release is much less.

Completion

We need to use appropriate techniques for each language. Trying to use a technique created for a language in others end up creating the misinterpretations of the type "goto should never be used" and "comments should be spread throughout the program detailing everything the program does", etc..

Certainly this post does not close the subject and it would be interesting to see additional information. I left a few points out to give other people a chance to complement.

I put in the Github for future reference.


This response is based on the one found in Where Did the notion of "one Return only" come from?

  • 22

    I never swallowed this story that "goto is always bad", but had never seen a practical case where it was clearly useful and without good alternatives. + 1

  • 1

    I had never really seen a practical example showing a benefit of using the goto. + 1

  • 1

    Another practical example are those used by csc where loops and flow instructions are replaced by goto’s.

  • 4

    That example of goto didn’t convince me. Just do if ( !funcao2(recurso) ) { funcao3(recurso); } release_resource(recurso); return; that the goto can be dispensed with, being even more evident what is the logic behind the flow of the function. If there is no better example, I only see the goto being indispensable in Assembly and bytecode. No wonder, BASIC is considered a bad language for beginners in programming and Java deprives the use of keyword goto.

  • 3

    When I ask the question about the goto you will see that it is not quite so. I did not want to give a full example because this question is not about goto.

  • @Bigown Ok. When posting, please inform the link.

  • 1

    "SESE almost always makes code more complex", "We have the cyclomatic complexity reduced when we make the flow finish as soon as possible" - in the English topic the only argument for these statements is when adding a local variable and uses it to do flow control. I don’t think that’s the case in every situation.

  • "In languages that make use of exceptions, SESE makes less sense" - in a maintenance you may want to add code immediately before the (preferably single) return point, for example a call to the log function or any other function.

  • Has developer who avoids negative logic (if (!condição)) for finding it difficult to read. This suggests a deficiency/commonality in programming logic and leads to a tendency to spread return's by the function when the negative condition is reversed to a positive one. Avoiding negative logic should not be an excuse for not applying SE.

  • 9

    @bigown really this prejudice that this or that should not be used always falls into the same: ignorance. It is like discussing whether with or without OOP is better, whether if without { } can or cannot, etc. The good programmer uses what is best at the time it is best, the average programmer only uses the little he dominates, and the bad programmer repeats what he hears from the majority, because he has no way of discerning.

  • 2

    The problem of using goto is that it makes it difficult to maintain the code as it fragments the "code execution flows". A code with multiple Otos can have code flow that starts at the top, goes down, goes back to the middle... This is usually undesirable. As in the example already given by @Piovezan, in this example it is possible to refactor the code to have a condition if/else much more readable and easy to maintain.

  • 3

    Cases where the benefits of using goto overcomes its harms are very restricted. So restricted that if you say that "goto it’s always bad", you’ll probably be wrong less than 1% of the time. :)

  • 1

    I exemplified the use of goto in cases such as this in this answer, years ago.

  • 2

    what’s wrong with: reading a goto LABEL and seek such LABEL, following the reading from it? Are there so many people who are so limited in our area? A self-respecting programmer needs to understand what the goto makes and understands how to read a program using it. Ridiculous thing that goto é sempre ruim.

  • 3

    @Andrade Nobody said it’s always bad, only 99% of the time. : ) The problem is that LABEL may be in literally anywhere, above or below the goto, perhaps at a point totally disconnected from the flow you were following when you arrived at the goto, for example in another function. Spaghetti-code is almost synonymous with code with many goto's. The problem is not that the programmer knows how to read a goto sporadic, rather than wanting to perpetuate it in new codes in an unbridled manner. In 99% of cases there are better and more predictable control structures to use.

  • 2

    @Marciano.Andrade 1 goto The problem is when we pick up a spaghetti code that keeps throwing the flow back and forth all the time. It’s a lot of time. It is almost impossible to know which variables were initialized and what their value is.

  • I always disagreed with this single Return, at least now I have a good argument. Even more so today when there are many Ides where code is debugged and you can see the cycle

Show 12 more comments

71

Warning: The @Maniero response (to @Maniero’s question :) is clearly correct. My answer is shorter and more direct, but merely complementary, in order to highlight a point that I consider important.

For languages at a higher level (with automatic memory management: C#, Java, Javascript, Python etc.) there is actually a consensus on do not use the format described in the question as "recommended".

Just take a look, for example, at the answers to this question of programmers.stackexchange (in English).

Keep a single return tends to make the method more complex, with various levels of ifs and elses. Already the "Early returning", on the contrary, it allows the programmer to "get rid" of conditions that are easier to identify, that require less processing (usually associated with pre-error conditions, or that do not allow the complete execution of the routine). Soon, facilitating the writing.

This also makes the code easier to understand, as the method ends up dividing itself naturally into two parts: a beginning composed of quick output preconditions, and the code that performs the processing itself, when the input conditions are met. Therefore, facilitating the maintenance.

An even better way would be to use explicit preconditions, or schedule-by-contract, in languages that support this (such as Eiffel, for example).

  • 7

    Excellent addition! Eric Lippert style :)

  • 2

    Perfect. A lot of people (myself included) have always taken as a basis the SESE practice for whatever programming language is used.

22

Discussing a parallel issue...

FEARS

As the question refers to any language, any situation... I remembered situations where the problem was not style, elegance, readability, etc. but the logical behavior of the function. The misuse of the Return, or the sight of a lot of Returns, gives a certain "fear of making a mistake", of appearing mysterious bugs...

I remember three typical situations:

  1. When the algorithm is complex, full of ramifications, and I wish to "prove" that it ends (classic computer problem)... I believe that we should also remember the rule of "for any ELSE if", that helps you algebraically check if you have Return for all situations.

  2. When there is recurrence: how will the recurrence stack look?

  3. When the language does not clearly establish the "All Out" after Return.

  • 1

    Excellent addition.

13

quick response would be in relation to Maintainability of code, the fact that there is only one Return helps to easily map the flow within the method or function

  • 9

    Increasing cyclomatic complexity facilitates flow mapping?

Browser other questions tagged

You are not signed in. Login or sign up in order to post.