Meilleure pratique si / retour

60

Je veux savoir ce qui est considéré comme le meilleur moyen de revenir quand j'ai une ifdéclaration.

Exemple 1:

public bool MyFunction()
{
   // Get some string for this example
   string myString = GetString();

   if (myString == null)
   {
      return false;
   }
   else
   {
      myString = "Name " + myString;
      // Do something more here...
      return true;
   }
}

Exemple 2:

public bool MyFunction()
{
   // Get some string for this example
   string myString = GetString();

   if (myString == null)
   {
      return false;
   }

   myString = "Name " + myString;
   // Do something more here...
   return true;
}

Comme vous pouvez le voir dans les deux exemples, la fonction reviendra, true/falsemais est-ce une bonne idée de mettre une elsedéclaration comme dans le premier exemple ou mieux de ne pas la mettre?

sed
la source
7
Si vous ne vérifiez que les erreurs dans le premier "if", vous feriez mieux de ne pas inclure "else", car les erreurs ne doivent pas être considérées comme faisant partie de la logique réelle.
Mert Akcakaya
2
Personnellement, je serais plus préoccupé par la fonction causant des effets secondaires, mais je suppose que ceci est juste un exemple mal choisi?
jk.
14
Pour moi, la première version est comme rejetant tous les étudiants dans la salle qui n'a pas terminé leurs devoirs, et une fois qu'ils sont partis, en disant au reste des étudiants « Maintenant , si vous avez terminé vos devoirs ... ». C'est logique, mais inutile. Puisque le conditionnel n’est plus vraiment un conditionnel, j’ai tendance à laisser tomber else.
Nicole
1
Quel est le "Faites quelque chose de plus ici" que vous voulez faire? Cela pourrait complètement changer la façon dont vous concevez votre fonction.
Benoît

Réponses:

81

L'exemple 2 est connu sous le nom de bloc de garde. Il est plus approprié de renvoyer / jeter une exception tôt si quelque chose ne va pas (paramètre incorrect ou état non valide). En logique normale, il est préférable d'utiliser l'exemple 1

Kemoda
la source
+1 - une bonne distinction et ce que j'allais répondre.
Telastyn le
3
@ pR0Ps a la même réponse ci-dessous et fournit un exemple de code expliquant pourquoi cela devient un chemin plus propre à suivre. +1 pour la bonne réponse.
Cette question a été posée à plusieurs reprises sur SO et, même si cela peut être controversé, c'est une bonne réponse. Beaucoup de gens qui ont été formés pour revenir seulement à la fin ont quelques problèmes avec ce concept.
Bill K
2
+1 Éviter de manière inappropriée les blocs de garde a tendance à causer un code de flèche.
Brian
Les deux exemples ne montrent-ils pas un bloc de garde?
ernie
44

Mon style personnel consiste à utiliser le simple ifpour les blocs de garde et le if/ elsedans le code de traitement de la méthode.

Dans ce cas, vous utilisez la myString == nullcondition de garde, j'aurais donc tendance à utiliser le ifmotif simple .

Considérons un code un peu plus compliqué:

Exemple 1:

public bool MyFunction(myString: string){

    //guard block
    if (myString == null){
        return false;
    }
    else{
        //processing block
        myString = escapedString(myString);

        if (myString == "foo"){
            //some processing here
            return false;
        }
        else{
            myString = "Name " + myString;
            //other stuff
            return true;
        }
    }
}

Exemple 2:

public bool MyFunction(myString: string){

    //guard block
    if (myString == null){
        return false;
    }

    //processing block
    myString = escapedString(myString);

    if (myString == "foo"){
        //some processing here
        return false;
    }
    else{
        myString = "Name " + myString;
        //other stuff
        return true;
    }
}

Dans l'exemple 1, la protection et le reste de la méthode sont sous la forme if/ else. Comparez cela à l'exemple 2, où le bloc de garde est sous la ifforme unique , alors que le reste de la méthode utilise la forme if/ else. Personnellement, je trouve que l’exemple 2 est plus facile à comprendre, tandis que l’exemple 1 semble désordonné et trop en retrait.

Notez qu'il s'agit d'un exemple artificiel et que vous pouvez utiliser des else ifinstructions pour le nettoyer, mais mon objectif est de montrer la différence entre les blocs de garde et le code de traitement de la fonction.

Un compilateur décent devrait quand même générer la même sortie pour les deux. La seule raison d'utiliser l'un ou l'autre est la préférence personnelle ou de se conformer au style du code existant.

3 tours
la source
18
L'exemple 2 serait encore plus facile à lire si vous vous débarrassiez de la seconde.
briddums
18

Personnellement, je préfère la deuxième méthode. J'ai l'impression que c'est plus court, moins indenté et plus facile à lire.

GSto
la source
+1 pour moins d'indentation. Ce geste est évident si vous avez plusieurs chèques
d.raev
15

Ma pratique personnelle est la suivante:

  • Je n’aime pas les fonctions avec plusieurs points de sortie, j’ai eu du mal à maintenir et à suivre, les modifications de code cassent parfois la logique interne car elle est intrinsèquement un peu bâclée. Lorsqu'il s'agit d'un calcul complexe, je crée une valeur de retour au début et la retourne à la fin. Cela me force à suivre attentivement chacun des chemins if-else, switch, etc., et à définir correctement la valeur aux emplacements appropriés. Je passe également un peu de temps à décider de définir une valeur de retour par défaut ou de la laisser non initialisée au début. Cette méthode est également utile lorsque la logique ou le type ou la signification de la valeur de retour change.

Par exemple:

public bool myFunction()
{
   // First parameter loading
   String myString = getString();

   // Location of "quick exits", see the second example
   // ...

   // declaration of external resources that MUST be released whatever happens
   // ...

   // the return variable (should think about giving it a default value or not) 
   // if you have no default value, declare it final! You will get compiler 
   // error when you try to set it multiple times or leave uninitialized!
   bool didSomething = false;

   try {
     if (myString != null)
     {
       myString = "Name " + myString;
       // Do something more here...

       didSomething = true;
     } else {
       // get other parameters and data
       if ( other conditions apply ) {
         // do something else
         didSomething = true;
       }
     }

     // Edit: previously forgot the most important advantage of this version
     // *** HOUSEKEEPING!!! ***

   } finally {

     // this is the common place to release all resources, reset all state variables

     // Yes, if you use try-finally, you will get here from any internal returns too.
     // As I said, it is only my taste that I like to have one, straightforward path 
     // leading here, and this works even if you don't use the try-finally version.

   }

   return didSomething;
}
  • La seule exception: "sortie rapide" au début (ou dans de rares cas, dans le processus). Si la logique de calcul réelle ne peut pas gérer une certaine combinaison de paramètres d'entrée et d'états internes, ou a une solution simple sans exécuter l'algorithme, l'ajout de tout le code dans des blocs (parfois profonds) n'aiderait en rien. Il s'agit d'un "état exceptionnel", qui ne fait pas partie de la logique de base. Je dois donc sortir du calcul dès que j'ai détecté. Dans ce cas, il n'y a pas d'autre branche, dans des conditions normales, l'exécution continue. (Bien sûr, l’expression «état exceptionnel» s’exprime mieux en lançant une exception, mais parfois c’est exagéré.)

Par exemple:

public bool myFunction()
{
   String myString = getString();

   if (null == myString)
   {
     // there is nothing to do if myString is null
     return false;
   } 

   myString = "Name " + myString;
   // Do something more here...

   // not using return value variable now, because the operation is straightforward.
   // if the operation is complex, use the variable as the previous example.

   return true;
}

La règle "une sortie" est également utile lorsque le calcul nécessite des ressources externes que vous devez libérer, ou indique que vous devez réinitialiser avant de quitter la fonction; parfois, ils sont ajoutés plus tard au cours du développement. Avec plusieurs sorties à l'intérieur de l'algorithme, il est beaucoup plus difficile d'étendre correctement toutes les branches. (Et si des exceptions peuvent se produire, la libération / réinitialisation doit également être bloquée, pour éviter les effets secondaires dans de rares cas exceptionnels ...).

Votre cas semble tomber dans la catégorie "sortie rapide avant le vrai travail", et je l'écrirais comme votre version de l'exemple 2.

Lorand Kedves
la source
9
+1 pour "je n'aime pas les fonctions comportant plusieurs points de sortie"
Corv1nus le
@LorandKedves - ajout d'un exemple - j'espère que cela ne vous dérange pas.
Matthew Flynn le
@ MatthewFlynn eh bien, si cela ne vous dérange pas que cela soit à l'opposé de ce que j'ai suggéré à la fin de ma réponse ;-) Je poursuis l'exemple, j'espère que ce sera finalement bénéfique pour nous deux :-)
Lorand Kedves
@ MatthewFlynn (désolé, je ne suis qu'un salaud crabby, et merci de m'aider à clarifier mon opinion. J'espère que vous aimez la version actuelle.)
Lorand Kedves
13
Parmi les fonctions avec plusieurs points de sortie et les fonctions avec une variable de résultat modifiable, la première me semble de loin la moindre des deux maux.
Jon Purdy
9

Je préfère utiliser des gardes partout où je peux pour deux raisons:

  1. Ils permettent une sortie rapide en fonction de certaines conditions.
  2. Supprimez la nécessité de déclarations if complexes et non nécessaires ultérieurement dans le code.

De manière générale, je préfère voir des méthodes où la fonctionnalité principale de la méthode est claire et minimale. Les blocs de garde aident à rendre cela visuel.

drekka
la source
5

J'aime l'approche "Fall Through":

public bool MyFunction()
{
   string myString = GetString();

   if (myString != null)
   {
     myString = "Name " + myString;
     return true;
    }
    return false;
}

L'action a une condition spécifique, tout le reste n'est que le retour par défaut «échec».

Nuit noire
la source
1

Si j'ai une seule condition, je ne passerais pas trop de temps à méditer sur le style. Mais si j'ai plusieurs conditions de garde, je préférerais style2

Picuture cela. Supposons que les tests sont complexes et que vous ne voulez vraiment pas les lier dans une seule condition if-ORed pour éviter toute complexité:

//Style1
if (this1 != Right)
{ 
    return;
}
else if(this2 != right2)
{
    return;
}
else if(this3 != right2)
{
    return;
}
else
{
    //everything is right
    //do something
    return;
}

contre

//Style 2
if (this1 != Right)
{ 
   return;
}
if(this2 != right2)
{
    return;
}
if(this3 != right2)
{
    return;
}


//everything is right
//do something
return;

Ici, il y a deux avantages principaux

  1. Vous séparez le code d’une seule fonction en deux blocs visuels: un bloc supérieur de validations (conditions de garde) et un bloc inférieur de code exécutable.

  2. Si vous devez ajouter / supprimer une condition, vous réduisez vos chances de tout gâcher dans l’échelle if-elseif-else.

Un autre avantage mineur est que vous avez une paire d’accolades de moins à entretenir.

DPD
la source
0

Cela devrait être une question qui sonne mieux.

If condition
  do something
else
  do somethingelse

s'exprime mieux alors

if condition
  do something
do somethingelse

pour les petites méthodes, cela ne fera pas beaucoup de différence, mais pour les grandes méthodes composées, cela peut rendre la compréhension plus difficile, car elle ne sera pas correctement séparée

José Valente
la source
3
Si une méthode est si longue que la seconde forme est difficile à comprendre, elle est trop longue.
kevin cline
7
Vos deux cas ne sont équivalents que s'ils do somethingincluent une déclaration. Sinon, le second code s'exécutera do somethingelsequel que soit le ifprédicat qui ne correspond pas au fonctionnement du premier bloc.
Adnan
C’est aussi ce que le PO expliquait dans ses exemples. Je ne fais que suivre la ligne de la question
José Valente
0

Je trouve l'exemple 1 irritant, car l'instruction return manquante à la fin d'une fonction renvoyant une valeur déclenche immédiatement le signe "Attendez, il y a quelque chose qui ne va pas". Donc, dans des cas comme celui-ci, je choisirais l'exemple 2.

Cependant, il y a généralement plus d'implication, en fonction du but de la fonction, par exemple la gestion des erreurs, la journalisation, etc. Je suis donc avec la réponse de Lorand Kedves à ce sujet, et j'ai généralement un point de sortie à la fin, même au prix coûtant. d'une variable de drapeau supplémentaire. Cela facilite la maintenance et les extensions ultérieures dans la plupart des cas.

Sécurise
la source
0

Lorsque votre ifinstruction revient toujours, il n'y a aucune raison d'utiliser une autre option pour le code restant dans la fonction. Cela ajoute des lignes et une indentation supplémentaires. L'ajout de code inutile rend la lecture plus difficile et, comme tout le monde le sait, la lecture du code est difficile .

briddums
la source
0

Dans votre exemple, le elseest évidemment inutile, MAIS ...

Lorsque vous parcourez rapidement les lignes de code, vos yeux se penchent généralement sur les accolades et les empreintes pour vous faire une idée du flux de code. avant qu'ils ne s'installent réellement sur le code lui-même. Par conséquent, écrire le else, et mettre des accolades et une indentation autour du second bloc de code permet au lecteur de voir plus rapidement qu'il s'agit d'une situation "A ou B", dans laquelle l'un ou l'autre bloc sera exécuté. C'est-à-dire que le lecteur voit les accolades et l'indentation AVANT de voir returnaprès l'initiale if.

À mon avis, il s’agit d’un des rares cas où l’ajout d’un élément redondant au code le rend PLUS lisible, pas moins.

Dawood dit réintégrer Monica
la source
0

Je préférerais l'exemple 2, car il est immédiatement évident que la fonction retourne quelque chose . Mais même plus que cela, je préférerais revenir d'un endroit, comme ceci:

public bool MyFunction()
{
    bool result = false;

    string myString = GetString();

    if (myString != nil) {
        myString = "Name " + myString;

        result = true;
    }

    return result;
}

Avec ce style je peux:

  1. Voyez tout de suite que je retourne quelque chose.

  2. Saisir le résultat de chaque appel de la fonction avec un seul point d'arrêt.

Caleb
la source
Ceci est similaire à la façon dont j'aborderais le problème. La seule différence est que j'utiliserais la variable de résultat pour capturer l'évaluation myString et l'utiliser comme valeur de retour.
Chuck Conway
@ChuckConway D'accord, mais j'essayais de m'en tenir au prototype du PO.
Caleb
0

Mon style personnel a tendance à aller

function doQuery(string) {
    if (!query(string)) {
        query("ABORT");
        return false;
    } // else
    if(!anotherquery(string) {
        query("ABORT");
        return false;
    } // else
    return true;
}

Utilisez les elseinstructions commentées pour indiquer le déroulement du programme et le garder lisible, tout en évitant les retraits massifs qui peuvent facilement atteindre l'écran si de nombreuses étapes sont impliquées.

Shadur
la source
1
Personnellement, j'espère ne jamais avoir à travailler avec votre code.
un CVn
Si vous avez plusieurs vérifications, cette méthode peut être un peu longue. Renvoyer chaque clause if revient à utiliser l'instruction goto pour ignorer des sections du code.
Chuck Conway
Si c'était un choix entre ceci et le code avec les elseclauses incluses, je choisirais ce dernier parce que le compilateur les ignorera également. Cela dépend else if toutefois du fait que l'indentation n'augmente pas davantage que l'original une fois, sinon , je serais d'accord avec vous. Mais mon choix serait de laisser tomber elsecomplètement si ce style était autorisé.
Mark Hurd
0

Je souhaiterai écrire

public bool SetUserId(int id)
{
   // Get user name from id
   string userName = GetNameById(id);

   if (userName != null)
   {
       // update local ID and userName
       _id = id;
       _userNameLabelText = "Name: " + userName;
   }

   return userName != null;
}

Je préfère ce style car il est évident que j'y retournerais userName != null.

tia
la source
1
La question est pourquoi préférez-vous ce style?
Caleb
... Franchement, je ne suis pas sûr de savoir pourquoi vous voulez même la chaîne dans ce cas, sans parler de la raison pour laquelle vous ajoutez un test booléen supplémentaire à la fin dont vous n’avez vraiment pas besoin pour quelque raison que ce soit.
Shadur
@Shadur Je suis conscient du test extra booléen et du myStringrésultat non utilisé . Je viens de copier la fonction de OP. Je vais le changer pour quelque chose qui ressemble plus à la vie réelle. Le test booléen est trivial et ne devrait pas être un problème.
tia
1
@Shadur Nous ne faisons pas d'optimisation ici, non? Mon but est de rendre mon code facile à lire et à maintenir, et personnellement, je le paierais avec le coût d'un chèque nul de référence.
tia
1
@tia j'aime l'esprit de votre code. Au lieu d'évaluer deux fois le nom d'utilisateur, je capturerais la première évaluation dans une variable et le renverrais.
Chuck Conway
-1

Ma règle générale est soit de faire un if-return sans autre (surtout des erreurs), soit de faire une chaîne complète if-else-if sur toutes les possibilités.

De cette façon, j'évite d'essayer d'être trop sophistiqué avec mes branches, car chaque fois que je finis par le faire, je code la logique d'application dans mes ifs, ce qui les rend plus difficiles à gérer (par exemple, si un enum OK ou ERR et que j'écris tous mes profitant du fait que! OK <-> ERR, il devient très pénible d’ajouter une troisième option à l’énum prochain.)

Dans votre cas, par exemple, j'utiliserais un simple if, puisque "return if null" est sûr d'être un modèle courant et qu'il est peu probable que vous ayez à vous soucier d'une troisième possibilité (en plus de null / non null) A l'avenir.

Cependant, si le test était quelque chose qui a fait une inspection plus approfondie sur les données, je serais plutôt orienté vers une analyse complète si-sinon.

hugomg
la source
-1

Je pense que l'exemple 2 présente de nombreux pièges qui pourraient éventuellement conduire à un code non souhaité. Le premier objectif est basé sur la logique entourant la variable "myString". Par conséquent, pour être explicite, tous les tests de conditions doivent avoir lieu dans un bloc de code tenant compte de la logique connue et par défaut / inconnue .

Et si par la suite le code était introduit involontairement dans l'exemple 2 qui modifiait la sortie de manière significative:

   if (myString == null)
   {
      return false;
   }

   //add some v1 update code here...
   myString = "And the winner is: ";
   //add some v2 update code here...
   //buried in v2 updates the following line was added
   myString = null;
   //add some v3 update code here...
   //Well technically this should not be hit because myString = null
   //but we already passed that logic
   myString = "Name " + myString;
   // Do something more here...
   return true;

Je pense qu'avec le elsebloc qui suivait immédiatement la recherche de null, les programmeurs ayant ajouté les améliorations aux versions futures ajouteraient toute la logique, car nous avons maintenant une chaîne de logique qui n'était pas voulue pour la règle d'origine (retour si la valeur est nul).

Je crois profondément en cela à certaines des directives C # sur Codeplex (lien vers celui-ci ici: http://csharpguidelines.codeplex.com/ ) qui indiquent ce qui suit:

"Ajoute un commentaire descriptif si le bloc par défaut (else) est supposé être vide. De plus, si ce bloc n’est pas censé être atteint, lève une exception InvalidOperationException pour détecter les modifications futures susceptibles de tomber dans les cas existants. Cela garantit un meilleur code, car tous les chemins que le code peut emprunter ont été pensés. "

Je pense que la bonne pratique de programmation lors de l’utilisation de blocs logiques comme celui-ci consiste à toujours ajouter un bloc par défaut (if-else, case: default) afin de comptabiliser explicitement tous les chemins de code et de ne pas laisser le code ouvert à des conséquences logiques non voulues.

atconway
la source
Comment ne pas avoir un autre exemple dans l'exemple deux ne prend pas tous les cas en considération? Le résultat souhaité est la poursuite de l'exécution. L'ajout d'une clause else dans ce cas ne fait qu'augmenter la complexité de la méthode.
Chuck Conway
Je suis en désaccord sur la base de la possibilité de ne pas avoir toute la logique concernée en question dans un bloc concis et déterministe. L'accent est mis sur la manipulation de la myStringvaleur et le code qui succède au ifbloc est la logique résultante si la chaîne! = Null. Par conséquent, puisque l’accent est mis sur la logique supplémentaire manipulant cette valeur spécifique , je pense qu’elle devrait être encapsulée dans un elsebloc. Autrement, cela ouvre un potentiel, comme je l’ai montré plus tôt, pour séparer involontairement la logique et introduire des conséquences inattendues. Cela n'arrivera peut-être pas tout le temps, mais c'était vraiment une question de style d'opinion basée sur les meilleures pratiques .
atconway