c# - tutorial - resharper visual studio 2015




Inverser l'instruction "if" pour réduire l'imbrication (16)

Quand j'ai lancé ReSharper sur mon code, par exemple:

    if (some condition)
    {
        Some code...            
    }

ReSharper m'a donné l'avertissement ci-dessus (Inverser "si" déclaration pour réduire l'imbrication), et a suggéré la correction suivante:

   if (!some condition) return;
   Some code...

Je voudrais comprendre pourquoi c'est mieux. J'ai toujours pensé qu'en utilisant "return" au milieu d'une méthode problématique, un peu comme "goto".


Éviter plusieurs points de sortie peut entraîner des gains de performance. Je ne suis pas sûr de C # mais en C ++, l'optimisation de la valeur de retour nommée (Copy Elision, ISO C ++ '03 12.8 / 15) dépend de l'existence d'un seul point de sortie. Cette optimisation évite la copie en construisant votre valeur de retour (dans votre exemple spécifique, cela n'a pas d'importance). Cela pourrait conduire à des gains de performance considérables dans les boucles serrées, car vous enregistrez un constructeur et un destructeur chaque fois que la fonction est invoquée.

Mais pour 99% des cas, sauvegarder les appels constructeurs et destructeurs supplémentaires ne vaut pas la perte de lisibilité imbriquée if blocs s'introduisent (comme d'autres l'ont souligné).


A mon avis, le retour anticipé est correct si vous ne faites que renvoyer un void (ou un code de retour inutile que vous ne vérifierez jamais) et cela pourrait améliorer la lisibilité car vous évitez l'imbrication et en même temps vous expliquez que votre fonction est terminée.

Si vous renvoyez réellement un returnValue, l'imbrication est généralement préférable, car vous renvoyez votre returnValue juste au même endroit (à la fin), ce qui peut rendre votre code plus facile à maintenir dans beaucoup de cas.


C'est bien sûr subjectif, mais je pense qu'il améliore fortement sur deux points:

  • Il est maintenant immédiatement évident que votre fonction n'a plus rien à faire si la condition est condition .
  • Il maintient le niveau d'imbrication bas. La nidification nuit à la lisibilité plus que vous ne le pensez.

C'est simplement controversé. Il n'y a pas «d'accord entre les programmeurs» sur la question du retour anticipé. C'est toujours subjectif, autant que je sache.

Il est possible de faire un argument de performance, car il est préférable d'avoir des conditions écrites de façon à ce qu'elles soient le plus souvent vraies; on peut aussi soutenir que c'est plus clair. D'un autre côté, il crée des tests imbriqués.

Je ne pense pas que vous obtiendrez une réponse concluante à cette question.


C'est une question d'opinion.

Mon approche habituelle serait d'éviter les ifs à une seule ligne, et revient au milieu d'une méthode.

Vous ne voudriez pas que des lignes comme cela le suggère partout dans votre méthode, mais il y a quelque chose à dire pour vérifier un tas d'hypothèses au sommet de votre méthode, et ne faire que votre travail réel si elles passent toutes.


Cela n'affecte pas seulement l'esthétique, mais empêche également l'imbrication du code.

Il peut en fait fonctionner comme une condition préalable pour garantir que vos données sont également valides.


En termes de performances, il n'y aura pas de différence notable entre les deux approches.

Mais le codage ne se limite pas à la performance. La clarté et la maintenabilité sont également très importantes. Et, dans les cas comme celui-ci où cela n'affecte pas la performance, c'est la seule chose qui compte.

Il existe des écoles de pensée concurrentes quant à l'approche préférable.

Une vue est celle que d'autres ont mentionnée: la seconde approche réduit le niveau d'imbrication, ce qui améliore la clarté du code. C'est naturel dans un style impératif: quand on n'a plus rien à faire, autant retourner tôt.

Une autre vue, du point de vue d'un style plus fonctionnel, est qu'une méthode ne devrait avoir qu'un seul point de sortie. Tout dans un langage fonctionnel est une expression. Donc, si les instructions doivent toujours avoir une clause else. Sinon, l'expression if n'aura pas toujours de valeur. Donc, dans le style fonctionnel, la première approche est plus naturelle.


En théorie, inverser si cela pourrait conduire à une meilleure performance si elle augmente le taux de succès de prédiction de branche. En pratique, je pense qu'il est très difficile de savoir exactement comment se comportera la prédiction de branchement, surtout après la compilation, donc je ne le ferais pas dans mon développement au jour le jour, sauf si j'écris du code assembleur.

Plus sur la prédiction de branche ici .


Il y a plusieurs avantages à ce type de codage mais pour moi la grande victoire est, si vous pouvez revenir rapidement, vous pouvez améliorer la vitesse de votre application. IE Je sais que grâce à la Condition X, je peux revenir rapidement avec une erreur. Cela élimine d'abord les cas d'erreur et réduit la complexité de votre code. Dans beaucoup de cas parce que le pipeline de cpu peut maintenant être plus propre, il peut arrêter les accidents de pipeline ou les commutateurs. Deuxièmement, si vous êtes dans une boucle, casser ou renvoyer rapidement peut vous faire économiser beaucoup de cpu. Certains programmeurs utilisent des invariants de boucle pour faire ce genre de sortie rapide mais dans ce cas vous pouvez casser votre pipeline de cpu et même créer un problème de recherche de mémoire et signifier que le cpu doit charger à partir du cache extérieur. Mais fondamentalement, je pense que vous devriez faire ce que vous vouliez, c'est-à-dire terminer la boucle ou la fonction et ne pas créer un chemin de code complexe juste pour implémenter une notion abstraite de code correct. Si le seul outil que vous avez est un marteau alors tout ressemble à un clou.


Il y a plusieurs bons points ici, mais plusieurs points de retour peuvent être illisibles aussi, si la méthode est très longue. Cela dit, si vous utilisez plusieurs points de retour, assurez-vous que votre méthode est courte, sinon le bonus de lisibilité de plusieurs points de retour peut être perdu.


Je voudrais ajouter qu'il y a un nom pour ceux qui sont inversés si - la clause de garde. Je l'utilise quand je peux.

Je déteste lire le code où il y a si au début, deux écrans de code et pas d'autre. Juste inverser si et retourner. De cette façon, personne ne perdra son temps à défiler.

http://c2.com/cgi/wiki?GuardClause


L'idée de ne revenir qu'à la fin d'une fonction revient des jours précédant la prise en charge par les langues des exceptions. Il permettait aux programmes de pouvoir mettre du code de nettoyage à la fin d'une méthode, puis d'être sûr qu'il serait appelé et qu'un autre programmeur ne cacherait pas un retour dans la méthode qui a fait sauter le code de nettoyage . Le code de nettoyage ignoré peut entraîner une fuite de mémoire ou de ressources.

Cependant, dans un langage qui supporte les exceptions, il n'offre pas de telles garanties. Dans un langage qui prend en charge les exceptions, l'exécution de n'importe quelle instruction ou expression peut provoquer un flux de contrôle qui provoque la fin de la méthode. Cela signifie que le nettoyage doit être effectué en utilisant les mots-clés finally ou using.

Quoi qu'il en soit, je dis que je pense que beaucoup de gens citent le principe du «retour à la fin d'une méthode» sans comprendre pourquoi c'était toujours une bonne chose à faire, et réduire l'imbrication pour améliorer la lisibilité est probablement un meilleur objectif.


Les clauses de protection ou les conditions préalables (comme vous pouvez probablement le voir) vérifient si une certaine condition est respectée et interrompent ensuite le déroulement du programme. Ils sont parfaits pour les endroits où vous n'êtes vraiment intéressé que par un résultat d'une instruction if . Alors plutôt que de dire:

if (something) {
    // a lot of indented code
}

Vous inversez la condition et cassez si cette condition inversée est remplie

if (!something) return false; // or another value to show your other code the function did not execute

// all the code from before, save a lot of tabs

return est loin d'être aussi sale que goto . Cela vous permet de passer une valeur pour montrer au reste de votre code que la fonction n'a pas pu s'exécuter.

Vous verrez les meilleurs exemples où cela peut être appliqué dans des conditions imbriquées:

if (something) {
    do-something();
    if (something-else) {
        do-another-thing();
    } else {
        do-something-else();
    }
}

contre:

if (!something) return;
do-something();

if (!something-else) return do-something-else();
do-another-thing();

Vous trouverez peu de gens qui prétendent que le premier est plus propre, mais bien sûr, c'est complètement subjectif. Certains programmeurs aiment savoir à quelles conditions quelque chose opère par indentation, alors que je préfère garder le flux de méthode linéaire.

Je ne vais pas suggérer un seul instant que les préconscences vont changer votre vie ou vous mettre au lit, mais vous pourriez trouver votre code un peu plus facile à lire.


Mon idée est que le retour "au milieu d'une fonction" ne devrait pas être aussi "subjectif". La raison est assez simple, prenez ce code:

    function do_something( data ){

      if (!is_valid_data( data )) 
            return false;


       do_something_that_take_an_hour( data );

       istance = new object_with_very_painful_constructor( data );

          if ( istance is not valid ) {
               error_message( );
                return ;

          }
       connect_to_database ( );
       get_some_other_data( );
       return;
    }

Peut-être que le premier "retour" n'est pas si intuitif, mais c'est vraiment une économie. Il y a trop d '«idées» sur les codes propres, qui ont simplement besoin de plus de pratique pour perdre leurs mauvaises idées «subjectives».


Plusieurs points de retour étaient un problème dans C (et dans une moindre mesure C ++) car ils vous obligeaient à dupliquer le code de nettoyage avant chacun des points de retour. Avec la collecte des ordures, l' try | finally construire et using blocs, il n'y a vraiment aucune raison pour laquelle vous devriez avoir peur d'eux.

En fin de compte, cela revient à ce que vous et vos collègues trouvez plus facile à lire.


Un retour au milieu de la méthode n'est pas forcément mauvais. Il pourrait être préférable de revenir immédiatement si cela rend l'intention du code plus claire. Par exemple:

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
     return result;
};

Dans ce cas, si _isDead est vrai, nous pouvons immédiatement sortir de la méthode. Il serait peut-être préférable de le structurer de cette façon:

double getPayAmount() {
    if (_isDead)      return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired)   return retiredAmount();

    return normalPayAmount();
};   

J'ai choisi ce code dans le catalogue de refactoring . Ce refactoring spécifique est appelé: Remplacer le conditionnel imbriqué par des clauses de protection.







resharper