web-dev-qa-db-fra.com

Comment effectuer une refactorisation propre d'un code If Else sans laisser de blocs libres?

if(condition1)
{
   Statement1A;
   Statement1B;
}
else if(condition2)
{
  Statement2;
}
else if(condition3)
{
  Statement3;
}
else 
{
   Statement1A;
   Statement1B;
}

   return;

Je voudrais refactoriser ce code afin de ne pas dupliquer les instructions. I toujours besoin de vérifier condition1 avant toute autre condition. (Je ne peux donc pas simplement changer l'ordre des conditions). Je ne veux pas non plus écrire &&!condition1 après toutes les autres conditions.

Je l'ai résolu comme ça

if(condition1)
{
}
else if(condition2)
{
  Statement2;
  return;
}
else if(condition3)
{
  Statement3;
  return;
}

Statement1A;
Statement1B;
return;

Cependant, je ne pense pas qu'une condition vide soit facilement compréhensible par les autres (même par moi après un certain temps).

Quelle est la meilleure approche?

13
M.C.
notCondition2And3 = !condition2 & !condition3; 
// in place of notCondition2And3 should be some meaningful name
// representing what it actually MEANS that neither condition2 nor condition3 were true

Et maintenant:

if (condition1 || notCondition2And3)
{
   Statement1A;
   Statement1B;
   return;
}
if (condition2)
{
   Statement2;
   return;
}
if (condition3)
{
   Statement3;
   return;
}

Comme je l'ai écrit dans mon commentaire à réponse de Kieveli , je ne vois rien de mal à propos de plusieurs returns dans une méthode, s'il n'y a pas de considérations de gestion de la mémoire (comme cela pourrait être le cas en C ou C++ où vous devez libérer toutes les ressources manuellement avant de partir).

Ou encore une autre approche. Voici la matrice de décision pour ne pas la gâcher:

F F F - 1
---------
F F T - 3
---------    
F T F - 2
F T T - 2
---------    
T F F - 1
T F T - 1
T T F - 1
T T T - 1

Ts et Fs représentent les valeurs de condition1, condition2 et condition3 (respectivement). Les chiffres représentent les résultats.

Il indique clairement qu'il est également possible d'écrire le code sous la forme:

if (!condition1 && condition2) // outcome 2 only possible for FTF or FTT, condition3 irrelevant
{
   Statement2;
   return;
}
if (!condition1 && !condition2 && condition3)  // outcome 3 only when FFT
{
   Statement3;
   return;
}
// and for the remaining 5 combinations...
Statement1A;
Statement1B;

Maintenant, si nous avons extrait !condition1 (qui est présent dans les deux ifs), on obtiendrait:

if (!condition1)
{
    if (condition2) // outcome 2 only possible for FTF or FTT, condition3 irrelevant
    {
       Statement2;
       return;
    }
    if (condition3)  // outcome 3 only when FFT
    {
       Statement3;
       return;
    }
}
// and for the remaining 5 combinations...
Statement1A;
Statement1B;

Ce qui est presque exactement ce que Kieveli a suggéré, seul son dédain pour les premiers return a rendu son implémentation boguée (comme il l'a noté lui-même), car cela ne ferait rien si les 3 conditions étaient fausses.

Ou, nous pourrions revenir comme ça (cela ne fonctionnerait probablement pas dans tous les langages - cela fonctionne en C #, pour un, puisque C # permet une comparaison d'égalité entre plusieurs variables), maintenant nous sommes pratiquement de retour au premier:

// note that "condition1" on the right side of || is actually redundant and can be removed, 
// because if the expression on the right side of || gets evaluated at all,
// it means that condition1 must have been false anyway:

if (condition1 || (condition1 == condition2 == condition3 == false)) // takes care of all 4 x T** plus FFF (the one special case). 
{
    Statement1A;
    Statement1B;
    return;
}
// and now it's Nice and clean
if (condition2) 
{
    Statement2;
    return; // or "else" if you prefer
}
if (condition3)
{
    Statement3;
    return; // if necessary
}
32
Konrad Morawski

Je ne suis pas fan de vos déclarations if et return vides. Cela devient difficile à suivre, et il est généralement mal vu d'avoir plusieurs déclarations de retour dans le corps de votre code. Compte tenu de vos objectifs, je ferais quelque chose comme ceci:

if ( ! condition1 )
{
   if ( condition2 )
      Statement2;
   else if ( condition3 )
      Statement3;
}
else
{
   Statement1A;
   Statement1B;
}

Remarque: cette solution est erronée! Considérez! Condition1 &&! Condition2 &&! Condition3

10
Kieveli

La répétition des instructions n'est un problème que dans la mesure où vous répliquez un aspect de votre code - dans ce cas, "faites Statement1A, puis faites Statement1B".

Une refactorisation plus propre consiste à déplacer chacun de ces blocs dans des méthodes distinctes, même si l'autre méthode ne comporte que deux lignes.

void _statement1() {
  Statement1A;
  Statement1B;
}

Avoir une telle méthode vous permet simplement de la référencer dans des endroits où vous vous seriez répété.

if(condition1) 
{
  _statement1()
} 
else if(condition2) 
{
  Statement2;
}
else if(condition3)
{
  Statement3;
}
else 
{
  _statement1()
}

Bien sûr, cela soulève la question suivante de "quand devrais-je utiliser une méthode au lieu de me répéter?", Qui a ses propres considérations. (Je préfère "autant que possible", mais je viole aussi de manière flagrante cette règle en fonction de la langue.)

6
DougM

J'aime cette façon:

        if (!condition1)
        {
            if (condition2)
            {
                Statement2();
                return;
            }
            else if (condition3)
            {
                Statement3();
                return;
            }
        }

        Statement1A();
        Statement1B();
        return;

MODIFIER :

J'aime encore plus cette façon parce que je n'ai qu'un seul retour et 2 instructions IF:

        if (!condition1 && condition2)
            Statement2();
        else if (!condition1 && condition3)
            Statement3();
        else
        {
            Statement1A();
            Statement1B();
        }
        return;
4
coutol

Peut-être que c'est juste moi, mais je trouve une condition comme if (!a && b) relativement difficile à lire, et préfère généralement if (b && !a), lorsque cela est possible (c'est-à-dire, sauf si je suis tributaire d'une évaluation de court-circuit donc b ne sera évalué que si a était faux).

Sur cette base, je l'écrirais probablement comme:

if (condition2 && !condition1)
   statement2;
else if (condition3 && !condition1) 
   statement3;
else {
    statement1A;
    statement1B;
}

Comme je l'ai dit cependant, bien que ce soit bien si vous ne vous souciez que des statements qui sont évalués, cela ne fonctionne pas si vous vous souciez également des conditions qui sont évalués.

2
Jerry Coffin

Il y a une autre façon. Je préférerais conserver le flux de contrôle d'origine. Ensuite, le travail qui est effectué deux fois, peut être mis dans une fonction, puis nous invoquons la fonction à partir de deux endroits différents:

function X()
{  
  if(condition1)
  {
     HandleDefault();
     return;
  }

  if(condition2)
  {
     Statement2;
     return;
  }

  if(condition3)
  {
    Statement3;
    return;
  }

  HandleDefault();
  return;

}
/// 


function HandleDefault(   )
{
    Statement1A;
    Statement1B;
}

Souvent, s'il y a des blocs d'instructions qui vont ensemble, il est possible de donner un nom significatif à la fonction, augmentant encore plus la lisibilité du code. Mais si vous n'avez pas de nom significatif, vous pouvez toujours créer une fonction privée avec un nom moins descriptif (comme je l'ai fait dans mon exemple).

La raison pour laquelle je pense que c'est la meilleure solution car elle conserve l'ordre des conditions, alors qu'elle permet de conserver une seule copie de Statement1A et Statement1B, et cela sans introduire de constructions d'imbrication ou de variables temporaires. Cela réduit la quantité de choses dont vous avez besoin de garder une trace mentale lors de la lecture de ce code.

Maintenant, il y a des langages (notamment C) où les premiers retours ne peuvent pas toujours être utilisés, mais dans tout autre langage courant, je pense à vous.

2
jdv-Jan de Vaan

Deux approches qui n'ont pas encore été mentionnées sont de réécrire les déclarations sous forme d'expressions (si elles s'y prêtent) ou (oserais-je le dire) en utilisant un petit mot qui commence par g. Il y a des cas où je considérerais chacun d'eux comme la meilleure alternative.

Dans le cas des expressions, le code serait quelque chose comme:

if (condition1 ||
     condition2 ? (action2(),1) :
     condition3 ? (action3(),1) :
     1)
  action1;

Plutôt méchant, et généralement pas quelque chose que j'utiliserais, mais cela peut être raisonnable dans les cas où condition2 et action2 sont toutes les deux encapsulées dans une méthode qui tente une opération et indique si elle réussit et de même condition2 et action3. Dans ce cas, il pourrait devenir:

if (cantTryAnything || (
     tryFirstThing() != FIRST_TRY_SUCCESS &&
     trySecondThing() != SECOND_TRY_SUCCESS
)
{
  default actions here
}

Effectuez les actions par défaut si je ne peux rien essayer ou si tout ce que j'ai essayé échoue.

Notez que simplement copier les actions ou les factoriser dans une méthode différente serait généralement préférable au style de logique ci-dessus ou à un g___. mais si le code dans les "actions par défaut ici" doit manipuler des variables locales et que son comportement exact peut changer, l'encapsulation du code dans une méthode externe peut être difficile et la duplication du code pose le risque que les versions en double soient modifiées de manière à pour ne plus correspondre parfaitement. En outre, la notion selon laquelle une certaine instruction de flux de contrôle est nuisible se concentre sur le fait que la plupart les problèmes de flux de contrôle du monde réel peuvent être bien mappés dans des constructions de langage courantes, et que quand un problème du monde réel correspond bien à une construction de langage, on devrait l'utiliser. Si le flux de programme particulier requis par une application nécessite un comportement identique dans plusieurs combinaisons de conditions, je suppose qu'il vaut mieux forcer explicitement l'exécution à un point commun dans toutes ces conditions que de contourner le problème.

0
supercat

Deux choses viennent à l'esprit :

Boîtier de commutation

bien qu'il ne soit pas toujours applicable, il peut faciliter la lecture et la maintenance du code. Capturez fondamentalement les différentes conditions dans une énumération et créez une fonction pour analyser le contexte afin de produire la valeur d'énumération correcte, puis changez-la et exécutez le code approprié.

vous réaliserez deux choses:

  • séparer le code logique de l'exécution. Cet isolement facilitera la correction des bogues dans chaque partie.
  • flux de contrôle facile à lire. La structure du commutateur est assez intuitive, surtout si vous nommez soigneusement les valeurs d'énumération.

Fonctions

Capturez chaque bloc de code dans une fonction et au début de la fonction, mettez du code pour décider si vous devez exécuter ou non.

Ici, vous n'obtenez pas la séparation mentionnée ci-dessus mais vous cassez la structure de flux de code IF-ELSE en bits plus gérables. De plus, si un bogue se produit, il sera plus rapide à corriger car il sera isolé dans un seul bloc de code.

Le plus grand avantage est cependant qu'un changement dans l'une de ces fonctions n'affectera pas les autres parties du code.

Les fonctions ont un avantage sur le boîtier du commutateur, c'est qu'elles peuvent être ajoutées dynamiquement. Une extension naturelle serait quelque chose de similaire au modèle de commande où une première partie du code analyse les actions de contexte et de files d'attente dans une liste, puis une deuxième partie exécute simplement ces actions séquentiellement.

Quelle que soit la stratégie choisie KEEP IT SIMPLE parfois une série de if-else est suffisante et doit rester telle quelle. En règle générale, si je reviens dans le même code pour résoudre ce qui semble être le même problème ou si je me retrouve souvent à me gratter la tête pour ajouter encore une nouvelle condition, alors il est peut-être temps de chercher des alternatives telles que celles mentionnées au dessus de.

0
Newtopian