web-dev-qa-db-fra.com

Dois-je revenir d'une fonction plus tôt ou utiliser une instruction if?

J'ai souvent écrit ce genre de fonction dans les deux formats, et je me demandais si un format est préféré à un autre, et pourquoi.

public void SomeFunction(bool someCondition)
{
    if (someCondition)
    {
        // Do Something
    }
}

ou

public void SomeFunction(bool someCondition)
{
    if (!someCondition)
        return;

    // Do Something
}

Je code habituellement avec le premier car c'est ainsi que mon cerveau fonctionne pendant le codage, bien que je pense que je préfère le 2ème car il s'occupe immédiatement de la gestion des erreurs et je le trouve plus facile à lire

302
Rachel

Je préfère le deuxième style. Éliminez d'abord les cas non valides, simplement en quittant ou en levant les exceptions selon le cas, mettez une ligne vierge à l'intérieur, puis ajoutez le "vrai" corps de la méthode. Je trouve cela plus facile à lire.

400
Mason Wheeler

Certainement ce dernier. Le premier n'a pas l'air mal en ce moment, mais lorsque vous obtenez un code plus complexe, je ne peux pas imaginer que quelqu'un pense cela:

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    int retval = SUCCESS;
    if (someCondition)
    {
        if (name != null && name != "")
        {
            if (value != 0)
            {
                if (perms.allow(name)
                {
                    // Do Something
                }
                else
                {
                    reval = PERM_DENY;
                }
            }
            else
            {
                retval = BAD_VALUE;
            }
        }
        else
        {
            retval = BAD_NAME;
        }
    }
    else
    {
        retval = BAD_COND;
    }
    return retval;
}

est plus lisible que

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    if (!someCondition)
        return BAD_COND;

    if (name == null || name == "")
        return BAD_NAME;

    if (value == 0)
        return BAD_VALUE;

    if (!perms.allow(name))
        return PERM_DENY;

    // Do something
    return SUCCESS;
}

J'admets pleinement que je n'ai jamais compris l'avantage des points de sortie uniques.

169
Jason Viers

Cela dépend - En général, je ne vais pas faire tout mon possible pour essayer de déplacer un tas de code pour sortir de la fonction plus tôt - le compilateur s'en occupe généralement pour moi. Cela dit cependant, s'il y a des paramètres de base au sommet dont j'ai besoin et que je ne peux pas continuer autrement, je m'exposerai tôt. De même, si une condition génère un bloc géant if dans la fonction, je le ferai aussi tôt en raison de cela.

Cela dit, si une fonction nécessite des données lors de son appel, je vais généralement lancer une exception (voir l'exemple) plutôt que de simplement revenir.

public int myFunction(string parameterOne, string parameterTwo) {
  // Can't work without a value
  if (string.IsNullOrEmpty(parameterOne)) {
    throw new ArgumentNullException("parameterOne");
  } 
  if (string.IsNullOrEmpty(parameterTwo)) {
    throw new ArgumentNullException("parameterTwo");
  }

  // ...      
  // Do some work
  // ...

  return value;
}
32
rjzii

Je préfère le retour anticipé.

Si vous avez un point d'entrée et un point de sortie, vous devez toujours suivre l'intégralité du code dans votre tête jusqu'au point de sortie (vous ne savez jamais si un autre morceau de code ci-dessous fait autre chose pour le résultat, donc vous doivent le suivre jusqu'à ce qu'il existe). Vous ne faites aucune question quelle branche détermine le résultat final. C'est difficile à suivre.

Avec une entrée et plusieurs existantes, vous revenez lorsque vous avez votre résultat et ne vous embêtez pas à le suivre tout le long pour voir que personne ne fait rien d'autre (car il n'y aura rien d'autre depuis votre retour). C'est comme si le corps de la méthode était divisé en plusieurs étapes, chaque étape ayant la possibilité de renvoyer le résultat ou de laisser l'étape suivante tenter sa chance.

24
user7197

Dans la programmation C où vous devez nettoyer manuellement, il y a beaucoup à dire pour un retour en un point. Même s'il n'est pas nécessaire pour le moment de nettoyer quelque chose, quelqu'un peut modifier votre fonction, allouer quelque chose et avoir besoin de le nettoyer avant de revenir. Si cela se produit, ce sera un travail de cauchemar en parcourant toutes les déclarations de retour.

En programmation C++, vous avez des destructeurs et même maintenant des gardes de sortie de portée. Tout cela doit être là pour s'assurer que le code est en premier lieu protégé contre les exceptions, donc le code est bien protégé contre une sortie anticipée et donc cela n'a pas d'inconvénient logique et est purement une question de style.

Je ne connais pas assez bien Java, si le code de bloc "finalement" sera appelé et si les finaliseurs peuvent gérer la situation de devoir s'assurer que quelque chose se passe.

C # Je ne peux certainement pas répondre.

Le langage D vous offre des protecteurs de sortie de portée intégrés appropriés et est donc bien préparé pour une sortie précoce et ne devrait donc pas poser de problème autre que le style.

Les fonctions ne devraient bien sûr pas être si longues en premier lieu, et si vous avez une énorme instruction switch, votre code est probablement également mal pris en compte.

13
CashCow

J'utilise les deux.

Si DoSomething est composé de 3 à 5 lignes de code, le code est tout simplement magnifique en utilisant la première méthode de formatage.

Mais s'il a beaucoup plus de lignes que cela, alors je préfère le deuxième format. Je n'aime pas quand les crochets d'ouverture et de fermeture ne sont pas sur le même écran.

9
azheglov

Retours précoces pour la victoire. Ils peuvent sembler moches, mais beaucoup moins laids que les gros wrappers if, surtout s'il y a plusieurs conditions à vérifier.

9
STW

Une raison classique pour une entrée unique-sortie unique est qu'autrement la sémantique formelle devient indiciblement laide autrement (la même raison pour laquelle GOTO était considéré comme nuisible).

Autrement dit, il est plus facile de déterminer quand votre logiciel quittera la routine si vous n'avez qu'un seul retour. Ce qui est également un argument contre les exceptions.

En règle générale, je minimise l'approche de retour anticipé.

8
Paul Nathan

Personnellement, je préfère faire des vérifications de condition de réussite/échec au début. Cela me permet de regrouper la plupart des échecs les plus courants en haut de la fonction avec le reste de la logique à suivre.

7
nathan

Ça dépend.

Retour anticipé s'il existe une condition d'impasse évidente à vérifier immédiatement, ce qui rendrait inutile l'exécution du reste de la fonction. *

Définissez Retval + retour unique si la fonction est plus complexe et pourrait avoir plusieurs points de sortie sinon (problème de lisibilité).

* Cela peut souvent indiquer un problème de conception. Si vous trouvez que beaucoup de vos méthodes doivent vérifier un état externe/paramétrique ou autre avant d'exécuter le reste du code, c'est probablement quelque chose qui devrait être géré par l'appelant. .

6
Bobby Tables

tilisez un If

Dans le livre de Don Knuth sur GOTO, je l'ai lu donner une raison de toujours avoir la condition la plus probable en premier dans une déclaration if. Sous l'hypothèse que c'est toujours une idée raisonnable (et non pas par pure considération pour la vitesse de l'époque). Je dirais que les retours précoces ne sont pas une bonne pratique de programmation, surtout compte tenu du fait qu'ils sont le plus souvent utilisés pour la gestion des erreurs, à moins que votre code ne soit plus susceptible d'échouer qu'échoué :-)

Si vous suivez les conseils ci-dessus, vous devrez mettre ce retour en bas de la fonction, et vous pourriez aussi bien ne pas l'appeler un retour là-bas, il suffit de définir le code d'erreur et de le renvoyer deux lignes plus loin. Réalisant ainsi l'idéal 1 entrée 1 sortie.

Spécifique Delphi ...

Je pense que c'est une bonne pratique de programmation pour les programmeurs Delphi, même si je n'ai aucune preuve. Avant D2009, nous n'avons pas de méthode atomique pour renvoyer une valeur, nous avons exit; et result := foo; ou nous pourrions simplement lever des exceptions.

Si vous deviez remplacer

if (true) {
 return foo;
} 

for

if true then 
begin
  result := foo; 
  exit; 
end;

vous pourriez simplement en avoir assez de voir cela au sommet de chacune de vos fonctions et préférez

if false then 
begin
  result := bar;

   ... 
end
else
   result := foo;

et évitez simplement exit.

3
Peter Turner

Je suis d'accord avec la déclaration suivante:

Je suis personnellement un fan des clauses de garde (le deuxième exemple) car cela réduit le retrait de la fonction. Certaines personnes ne les aiment pas car cela entraîne plusieurs points de retour de la fonction, mais je pense que c'est plus clair avec eux.

Tiré de cette question dans stackoverflow .

2
Toto

Je préfère écrire:

if(someCondition)
{
    SomeFunction();
}
1
Josh K

Comme vous, j'écris habituellement le premier, mais je préfère le dernier. Si j'ai beaucoup de chèques imbriqués, je refactorise généralement la deuxième méthode.

Je n'aime pas comment la gestion des erreurs est éloignée du chèque.

if not error A
  if not error B
    if not error C
      // do something
    else handle error C
  else handle error B
else handle error A

Je préfère ça:

if error A
  handle error A; return
if error B
  handle error B; return
if error C
  handle error C; return

// do something
1
jolt

J'utilise les retours précoces presque exclusivement de nos jours, à l'extrême. J'écris ceci

self = [super init];

if (self != nil)
{
    // your code here
}

return self;

comme

self = [super init];
if (!self)
    return;

// your code here

return self;

mais ça n'a pas vraiment d'importance. Si vous avez plus d'un ou deux niveaux d'imbrication dans vos fonctions, ils doivent être activés.

1
Dan Rosenstark

Les conditions au sommet sont appelées "conditions préalables". En mettant if(!precond) return;, vous listez visuellement toutes les conditions préalables.

L'utilisation du grand bloc "if-else" peut augmenter les frais généraux de retrait (j'ai oublié la citation sur les retraits à 3 niveaux).

0
Ming-Tang