language-agnostic - Une fonction devrait-elle avoir une seule déclaration de retour?




coding-style (25)

Y a-t-il de bonnes raisons pour lesquelles il est préférable d'avoir une seule déclaration de retour dans une fonction?

Ou est-ce correct de revenir d'une fonction dès qu'il est logique de le faire, ce qui signifie qu'il peut y avoir plusieurs instructions de retour dans la fonction?


Answers

Y a-t-il de bonnes raisons pour lesquelles il est préférable d'avoir une seule déclaration de retour dans une fonction?

Oui , il y a:

  • Le point de sortie unique donne un excellent endroit pour affirmer vos post-conditions.
  • Pouvoir mettre un point d'arrêt du débogueur sur le retour à la fin de la fonction est souvent utile.
  • Moins de retours signifie moins de complexité. Le code linéaire est généralement plus simple à comprendre.
  • Si essayer de simplifier une fonction à un seul retour entraîne une complexité, il est alors intéressant de refactoriser des fonctions plus petites, plus générales et plus faciles à comprendre.
  • Si vous êtes dans une langue sans destructeur ou si vous n'utilisez pas RAII, alors un seul retour réduit le nombre de places à nettoyer.
  • Certaines langues nécessitent un seul point de sortie (par exemple, Pascal et Eiffel).

La question est souvent posée comme une fausse dichotomie entre des déclarations multiples ou des déclarations si imbriquées. Il y a presque toujours une troisième solution très linéaire (pas d'imbrication profonde) avec un seul point de sortie.

Mise à jour : Apparemment, les directives MISRA favorisent la sortie unique .

Pour être clair, je ne dis pas que c'est toujours faux d'avoir plusieurs retours. Mais étant donné des solutions par ailleurs équivalentes, il y a beaucoup de bonnes raisons de préférer celle avec un seul retour.


You know the adage - beauty is in the eyes of the beholder .

Some people swear by NetBeans and some by IntelliJ IDEA , some by Python and some by PHP .

In some shops you could lose your job if you insist on doing this:

public void hello()
{
   if (....)
   {
      ....
   }
}

The question is all about visibility and maintainability.

I am addicted to using boolean algebra to reduce and simplify logic and use of state machines. However, there were past colleagues who believed my employ of "mathematical techniques" in coding is unsuitable, because it would not be visible and maintainable. And that would be a bad practice. Sorry people, the techniques I employ is very visible and maintainable to me - because when I return to the code six months later, I would understand the code clearly rather seeing a mess of proverbial spaghetti.

Hey buddy (like a former client used to say) do what you want as long as you know how to fix it when I need you to fix it.

I remember 20 years ago, a colleague of mine was fired for employing what today would be called agile development strategy. He had a meticulous incremental plan. But his manager was yelling at him "You can't incrementally release features to users! You must stick with the waterfall ." His response to the manager was that incremental development would be more precise to customer's needs. He believed in developing for the customers needs, but the manager believed in coding to "customer's requirement".

We are frequently guilty for breaking data normalization, MVP and MVC boundaries. We inline instead of constructing a function. We take shortcuts.

Personally, I believe that PHP is bad practice, but what do I know. All the theoretical arguments boils down to trying fulfill one set of rules

quality = precision, maintainability and profitability.

All other rules fade into the background. And of course this rule never fades:

Laziness is the virtue of a good programmer.


I believe that multiple returns are usually good (in the code that I write in C#). The single-return style is a holdover from C. But you probably aren't coding in C.

There is no law requiring only one exit point for a method in all programming languages . Some people insist on the superiority of this style, and sometimes they elevate it to a "rule" or "law" but this belief is not backed up by any evidence or research.

More than one return style may be a bad habit in C code, where resources have to be explicitly de-allocated, but languages such as Java, C#, Python or JavaScript that have constructs such as automatic garbage collection and try..finally blocks (and using blocks in C#), and this argument does not apply - in these languages, it is very uncommon to need centralised manual resource deallocation.

There are cases where a single return is more readable, and cases where it isn't. See if it reduces the number of lines of code, makes the logic clearer or reduces the number of braces and indents or temporary variables.

Therefore, use as many returns as suits your artistic sensibilities, because it is a layout and readability issue, not a technical one.

I have talked about this at greater length on my blog .


La programmation structurée indique que vous ne devriez avoir qu'une déclaration de retour par fonction. C'est pour limiter la complexité. Beaucoup de gens comme Martin Fowler soutiennent qu'il est plus simple d'écrire des fonctions avec plusieurs déclarations de retour. Il présente cet argument dans le livre de refactoring classique qu'il a écrit. Cela fonctionne bien si vous suivez ses autres conseils et écrivez de petites fonctions. Je suis d'accord avec ce point de vue et seuls les puristes de la programmation structurée stricte adhèrent à des déclarations de rendement unique par fonction.


Single exit point - all other things equal - makes code significantly more readable. But there's a catch: popular construction

resulttype res;
if if if...
return res;

is a fake, "res=" is not much better than "return". It has single return statement, but multiple points where function actually ends.

If you have function with multiple returns (or "res="s), it's often a good idea to break it into several smaller functions with single exit point.


Je dirais que vous devriez avoir autant que nécessaire, ou tout ce qui rend le code plus propre (comme les clauses de garde ).

Personnellement, je n'ai jamais entendu / vu de "bonnes pratiques" dire que vous devriez avoir une seule déclaration de retour.

Dans la plupart des cas, j'ai tendance à quitter une fonction dès que possible sur la base d'un chemin logique (les clauses de garde en sont un excellent exemple).


Dans une fonction qui n'a pas d'effets secondaires, il n'y a pas de bonne raison d'avoir plus d'un seul retour et vous devriez les écrire dans un style fonctionnel. Dans une méthode avec des effets secondaires, les choses sont plus séquentielles (indexées dans le temps), donc vous écrivez dans un style impératif, en utilisant l'instruction return comme une commande pour arrêter l'exécution.

En d'autres termes, quand c'est possible, privilégiez ce style

return a > 0 ?
  positively(a):
  negatively(a);

sur ceci

if (a > 0)
  return positively(a);
else
  return negatively(a);

Si vous vous trouvez en train d'écrire plusieurs couches de conditions imbriquées, il y a probablement un moyen de refactoriser cela, en utilisant une liste de prédicats par exemple. Si vous trouvez que vos ifs et elses sont très éloignés syntaxiquement, vous voudrez peut-être décomposer cela en plus petites fonctions. Un bloc conditionnel qui couvre plus d'un écran de texte est difficile à lire.

Il n'y a pas de règle absolue qui s'applique à toutes les langues. Quelque chose comme avoir une seule déclaration de retour ne rendra pas votre code bon. Mais bon code aura tendance à vous permettre d'écrire vos fonctions de cette façon.


Plus vous avez de déclarations dans une fonction, plus la complexité de cette méthode est élevée. Si vous vous demandez si vous avez trop d'instructions de retour, vous pouvez vous demander si vous avez trop de lignes de code dans cette fonction.

Mais, non, il n'y a rien de mal avec un / plusieurs déclarations de retour. Dans certaines langues, c'est une meilleure pratique (C ++) que dans d'autres (C).


I vote for Single return at the end as a guideline. This helps a common code clean-up handling ... For example, take a look at the following code ...

void ProcessMyFile (char *szFileName)
{
   FILE *fp = NULL;
   char *pbyBuffer = NULL:

   do {

      fp = fopen (szFileName, "r");

      if (NULL == fp) {

         break;
      }

      pbyBuffer = malloc (__SOME__SIZE___);

      if (NULL == pbyBuffer) {

         break;
      }

      /*** Do some processing with file ***/

   } while (0);

   if (pbyBuffer) {

      free (pbyBuffer);
   }

   if (fp) {

      fclose (fp);
   }
}

Je travaille actuellement sur une base de code où deux des personnes qui travaillent dessus s'abonnent aveuglément à la théorie du «point unique de sortie» et je peux vous dire que par expérience, c'est une horrible pratique horrible. Cela rend le code extrêmement difficile à maintenir et je vais vous montrer pourquoi.

Avec la théorie du «point de sortie unique», vous obtenez inévitablement un code qui ressemble à ceci:

function()
{
    HRESULT error = S_OK;

    if(SUCCEEDED(Operation1()))
    {
        if(SUCCEEDED(Operation2()))
        {
            if(SUCCEEDED(Operation3()))
            {
                if(SUCCEEDED(Operation4()))
                {
                }
                else
                {
                    error = OPERATION4FAILED;
                }
            }
            else
            {
                error = OPERATION3FAILED;
            }
        }
        else
        {
            error = OPERATION2FAILED;
        }
    }
    else
    {
        error = OPERATION1FAILED;
    }

    return error;
}

Non seulement cela rend le code très difficile à suivre, mais maintenant, disons plus tard, vous devez revenir en arrière et ajouter une opération entre 1 et 2. Vous devez mettre en retrait à peu près toute la fonction de panique, et bonne chance en vous assurant vos conditions if / else et vos accolades sont correctement mises en correspondance.

Cette méthode rend la maintenance du code extrêmement difficile et sujette aux erreurs.


Je l'ai vu dans les normes de codage pour C ++ qui étaient un hang-over de C, comme si vous n'aviez pas RAII ou toute autre gestion automatique de la mémoire, alors vous devez nettoyer pour chaque retour, ce qui signifie couper et coller du nettoyage ou d'un goto (logiquement le même que 'finally' dans les langages gérés), les deux étant considérés comme mauvais. Si vos pratiques consistent à utiliser des pointeurs et des collections intelligents en C ++ ou dans un autre système de mémoire automatique, il n'y a pas de raison majeure pour cela, et il s'agit uniquement de la lisibilité, et plus d'un jugement.


If you end up with more than a few returns there may be something wrong with your code. Otherwise I would agree that sometimes it is nice to be able to return from multiple places in a subroutine, especially when it make the code cleaner.

Perl 6: Bad Example

sub Int_to_String( Int i ){
  given( i ){
    when 0 { return "zero" }
    when 1 { return "one" }
    when 2 { return "two" }
    when 3 { return "three" }
    when 4 { return "four" }
    ...
    default { return undef }
  }
}

would be better written like this

Perl 6: Good Example

@Int_to_String = qw{
  zero
  one
  two
  three
  four
  ...
}
sub Int_to_String( Int i ){
  return undef if i < 0;
  return undef unless i < @Int_to_String.length;
  return @Int_to_String[i]
}

Note this is was just a quick example


Comme le note Kent Beck en discutant des clauses de garde dans les modèles de mise en œuvre, une routine a un seul point d'entrée et de sortie ...

"était d'empêcher la confusion possible en sautant et en sortant de nombreux endroits dans la même routine.Il était logique de l'appliquer aux programmes FORTRAN ou en assembleur écrits avec beaucoup de données globales où même comprendre quelles instructions étaient exécutées était un travail difficile. Avec de petites méthodes et surtout des données locales, il est inutilement conservateur. "

Je trouve une fonction écrite avec des clauses de garde beaucoup plus facile à suivre qu'une longue série imbriquée d'instructions if then else .


I've worked with terrible coding standards that forced a single exit path on you and the result is nearly always unstructured spaghetti if the function is anything but trivial -- you end up with lots of breaks and continues that just get in the way.


This is probably an unusual perspective, but I think that anyone who believes that multiple return statements are to be favoured has never had to use a debugger on a microprocessor that supports only 4 hardware breakpoints. ;-)

While the issues of "arrow code" are completely correct, one issue that seems to go away when using multiple return statements is in the situation where you are using a debugger. You have no convenient catch-all position to put a breakpoint to guarantee that you're going to see the exit and hence the return condition.


My usual policy is to have only one return statement at the end of a function unless the complexity of the code is greatly reduced by adding more. In fact, I'm rather a fan of Eiffel, which enforces the only one return rule by having no return statement (there's just a auto-created 'result' variable to put your result in).

There certainly are cases where code can be made clearer with multiple returns than the obvious version without them would be. One could argue that more rework is needed if you have a function that is too complex to be understandable without multiple return statements, but sometimes it's good to be pragmatic about such things.


J'ai souvent plusieurs déclarations au début d'une méthode pour retourner dans des situations "faciles". Par exemple, ceci:

public void DoStuff(Foo foo)
{
    if (foo != null)
    {
        ...
    }
}

... peut être rendu plus lisible (IMHO) comme ceci:

public void DoStuff(Foo foo)
{
    if (foo == null) return;

    ...
}

Alors oui, je pense que c'est bien d'avoir plusieurs "points de sortie" d'une fonction / méthode.


Avoir un seul point de sortie offre un avantage dans le débogage, car il vous permet de définir un seul point d'arrêt à la fin d'une fonction pour voir quelle valeur va réellement être retournée.


There are good things to say about having a single exit-point, just as there are bad things to say about the inevitable "arrow" programming that results.

If using multiple exit points during input validation or resource allocation, I try to put all the 'error-exits' very visibly at the top of the function.

Both the Spartan Programming article of the "SSDSLPedia" and the single function exit point article of the "Portland Pattern Repository's Wiki" have some insightful arguments around this. Also, of course, there is this post to consider.

If you really want a single exit-point (in any non-exception-enabled language) for example in order to release resources in one single place, I find the careful application of goto to be good; see for example this rather contrived example (compressed to save screen real-estate):

int f(int y) {
    int value = -1;
    void *data = NULL;

    if (y < 0)
        goto clean;

    if ((data = malloc(123)) == NULL)
        goto clean;

    /* More code */

    value = 1;
clean:
   free(data);
   return value;
}

Personally I, in general, dislike arrow programming more than I dislike multiple exit-points, although both are useful when applied correctly. The best, of course, is to structure your program to require neither. Breaking down your function into multiple chunks usually help :)

Although when doing so, I find I end up with multiple exit points anyway as in this example, where some larger function has been broken down into several smaller functions:

int g(int y) {
  value = 0;

  if ((value = g0(y, value)) == -1)
    return -1;

  if ((value = g1(y, value)) == -1)
    return -1;

  return g2(y, value);
}

Depending on the project or coding guidelines, most of the boiler-plate code could be replaced by macros. As a side note, breaking it down this way makes the functions g0, g1 ,g2 very easy to test individually.

Obviously, in an OO and exception-enabled language, I wouldn't use if-statements like that (or at all, if I could get away with it with little enough effort), and the code would be much more plain. And non-arrowy. And most of the non-final returns would probably be exceptions.

En bref;

  • Few returns are better than many returns
  • More than one return is better than huge arrows, and guard clauses are generally ok.
  • Exceptions could/should probably replace most 'guard clauses' when possible.

Je penche pour l'idée que les déclarations de retour au milieu de la fonction sont mauvaises. Vous pouvez utiliser des retours pour construire quelques clauses de garde en haut de la fonction, et bien sûr dire au compilateur ce qu'il faut retourner à la fin de la fonction sans problème, mais les retours au milieu de la fonction peuvent être facilement manqués et peuvent rendre la fonction plus difficile à interpréter.


Je dirais qu'il serait incroyablement imprudent de décider arbitrairement contre plusieurs points de sortie car j'ai trouvé que la technique était utile dans la pratique encore et encore , en fait j'ai souvent refaçonné le code existant à plusieurs points de sortie pour plus de clarté. Nous pouvons comparer les deux approches ainsi:

string fooBar(string s, int? i) {
  string ret = "";
  if(!string.IsNullOrEmpty(s) && i != null) {
    var res = someFunction(s, i);

    bool passed = true;
    foreach(var r in res) {
      if(!r.Passed) {
        passed = false;
        break;
      }
    }

    if(passed) {
      // Rest of code...
    }
  }

  return ret;
}

Comparez cela au code où plusieurs points de sortie sont autorisés: -

string fooBar(string s, int? i) {
  var ret = "";
  if(string.IsNullOrEmpty(s) || i == null) return null;

  var res = someFunction(s, i);

  foreach(var r in res) {
      if(!r.Passed) return null;
  }

  // Rest of code...

  return ret;
}

Je pense que ce dernier est considérablement plus clair. Autant que je sache, la critique de plusieurs points de sortie est un point de vue plutôt archaïque ces temps-ci.


Avoir un seul point de sortie réduit la complexité cyclomatique et donc, en théorie , réduit la probabilité que vous introduisiez des bogues dans votre code lorsque vous le modifiez. La pratique cependant tend à suggérer qu'une approche plus pragmatique est nécessaire. J'ai donc tendance à viser un seul point de sortie, mais permets à mon code d'en avoir plusieurs si c'est plus lisible.


Ma préférence serait pour une seule sortie à moins que cela ne complique vraiment les choses. J'ai trouvé que dans certains cas, plusieurs points d'existence peuvent masquer d'autres problèmes de conception plus importants:

public void DoStuff(Foo foo)
{
    if (foo == null) return;
}

En voyant ce code, je demanderais immédiatement:

  • Est-ce que 'foo' est toujours nul?
  • Si oui, combien de clients de 'DoStuff' appellent la fonction avec un 'foo' nul?

Selon les réponses à ces questions, il se pourrait que

  1. la vérification est inutile car elle n'est jamais vraie (c'est-à-dire qu'elle devrait être une assertion)
  2. la vérification est très rarement vraie et il peut donc être préférable de changer ces fonctions d'appel spécifiques car elles devraient probablement prendre d'autres mesures de toute façon.

Dans les deux cas ci-dessus, le code peut probablement être retravaillé avec une assertion pour s'assurer que 'foo' n'est jamais nul et que les appelants concernés ont changé.

Il y a deux autres raisons (spécifiques au code C ++, je pense) où les multiples existent peuvent avoir un effet négatif . Ce sont la taille du code et les optimisations du compilateur.

Un objet C ++ non-POD dans la portée à la sortie d'une fonction aura son destructeur appelé. Lorsqu'il y a plusieurs déclarations de retour, il peut y avoir des objets différents dans la portée et donc la liste des destructeurs à appeler sera différente. Le compilateur a donc besoin de générer du code pour chaque déclaration de retour:

void foo (int i, int j) {
  A a;
  if (i > 0) {
     B b;
     return ;   // Call dtor for 'b' followed by 'a'
  }
  if (i == j) {
     C c;
     B b;
     return ;   // Call dtor for 'b', 'c' and then 'a'
  }
  return 'a'    // Call dtor for 'a'
}

Si la taille du code est un problème - alors cela peut être quelque chose qui vaut la peine d'être évité.

L'autre problème se rapporte à "Named Return Value OptimiZation" (alias Copy Elision, ISO C ++ '03 12.8 / 15). C ++ permet à une implémentation d'ignorer l'appel du constructeur de copie s'il peut:

A foo () {
  A a1;
  // do something
  return a1;
}

void bar () {
  A a2 ( foo() );
}

En prenant simplement le code tel quel, l'objet 'a1' est construit dans 'foo' et ensuite sa construction de copie sera appelée pour construire 'a2'. Cependant, l'élision de la copie permet au compilateur de construire 'a1' au même endroit de la pile que 'a2'. Il n'y a donc pas besoin de "copier" l'objet lorsque la fonction revient.

Plusieurs points de sortie compliquent le travail du compilateur en essayant de détecter cela, et au moins pour une version relativement récente de VC ++, l'optimisation n'a pas eu lieu là où le corps de la fonction avait plusieurs retours. Voir Optimisation de la valeur de retour nommée dans Visual C ++ 2005 pour plus de détails.


Non, parce que nous ne vivons plus dans les années 1970 . Si votre fonction est suffisamment longue pour que plusieurs retours soient un problème, c'est trop long.

(Mis à part le fait que toute fonction multiligne dans une langue avec des exceptions aura de multiples points de sortie de toute façon.)


How about mate , mateWith , or coitus , for those who abide. In terms of reproducing mammals are generally considered immutable.

Going to throw Union out there too. Borrowed from SQL.







language-agnostic coding-style