web-dev-qa-db-fra.com

Comment s'attaquer à un anti-motif de la tête de flèche «ramifiée»?

J'ai récemment lu ceci question qui fonctionne, la flèche anti-motif.

J'ai quelque chose de similaire dans le code que j'essaie de refracteur, sauf que cela branche. Il semble un petit quelque chose comme ça:

if(fooSuccess==true&&barSuccess!=true){
    if(mooSuccess==true){
           .....
    }else if (mooSuccess!=true){
           .....
    }
}else if(fooSuccess!=true&&barSuccess==true){
    if(mooSuccess==true){
           .....
    }else if (mooSuccess!=true){
        if(cowSuccess==true){
                    .....
        }else if (cowSuccess!=true){
                    .....
        }
    }
}
......

En bref, ça ressemble à ceci

if
   if
     if
       if
         do something
       endif
    else if
     if
       if
         do something
        endif
    endif
    else if
     if
       if
         do something
        endif
    endif
 endif

Contour emprunté à partir de codage horreur article sur le sujet

Et le code passe à travers différentes permutations de true et de faux pour divers drapeaux. Ces drapeaux sont définis quelque part "UP" dans le code ailleurs, soit par entrée de l'utilisateur, soit par le résultat d'une méthode.

Comment puis-je faire ce genre de code plus lisible? Mon intention est que, finalement, j'aurai un objet de type bean contenant tous les choix que le programmeur précédent a tenté de capturer avec cet anti-motif de ramification. Par exemple, si dans le contour du code ci-dessus, nous n'avons vraiment que trois, j'ai un énumé dans ce haricot:

enum ProgramRouteEnum{
    OPTION_ONE,OPTION_TWO,OPTION_THREE;

    boolean choice;
    void setChoice(boolean myChoice){
        choice = myChoice;
    }
    boolean getChoice(){
        return choice;
    }
}

Est-ce un traitement acceptable? Ou y a-t-il un meilleur?

26
Pureferret

Le code peut être simplifié comme ceci:

enter image description here

boolean condA = ( fooSuccess  && !barSuccess &&  mooSuccess )
boolean condB = ( fooSuccess  && !barSuccess && !mooSuccess )
boolean condC = (!fooSuccess  &&  barSuccess &&  mooSuccess )
boolean condD = (!fooSuccess  &&  barSuccess && !mooSuccess &&  cowSuccess)
boolean condE = (!fooSuccess  &&  barSuccess && !mooSuccess && !cowSuccess)

if (condA) {
    ....
    return;
}
if (condB) {
    ....
    return;
}

... and so son 

if (condE) {
    ....
    return;
}

Essentiellement:

  • Évaluer des conditions complexes en un seul booléen
  • , car les conditions sont exclusives (comme c'est généralement le cas dans les têtes de flèche imbriquées) Vous n'avez pas à nier l'IFS, ajoutez simplement un retour pour que non autre bloc est exécuté.
  • Cela réduit énormément la complexité cyclomatique et rend le code trivial.
  • Avoir ce qu'il faut faire exactement dans tous les cas, vous pouvez même faire une Table Karnaugh pour identifier et supprimer des combinaisons de condition redondantes, comme ils le font dans des concepts de circuits logiques. Mais vous n'avez pas fourni cela dans l'exemple.
  • Assurez-vous d'extraire toute cette logique dans sa propre méthode afin que les déclarations de retour n'interfèrent pas avec des blocs qui doivent être exécutés.
  • Les noms descriptifs doivent être choisis pour conA..condF.
45
Tulains Córdova

ser61852 a une assez bonne réponse résolvant le problème général de simplifier les conditionnels imbriqués. Cependant, j'étais intrigué avec votre solution d'état d'état proposé et voulu illustrer comment cela peut parfois être préférable à un ensemble de drapeaux binaires.

Tout dépend de la séquence d'obtention des valeurs des indicateurs et du nombre de combinaisons valides. Si vous avez n drapeaux, vous avez 2n États. Pour 4 drapeaux, c'est 16 états et comme vous l'avez observé, seuls 3 d'entre eux peuvent avoir une signification utile. Dans des situations comme ça, l'utilisation d'une variable d'état peut plutôt simplifier les choses grandement.

Disons que vous avez un verrou qui ouvrira si 4 clés sont pressées dans le bon ordre. Si vous appuyez sur une touche de manière incorrecte, elle se réinitialise immédiatement au début de la séquence. Un moyen très naïf pour mettre en œuvre un gestionnaire de frappes utilisant des drapeaux binaires est:

void key_pressed(char key)
{
   if (!key1correct)
   {
      if (key == pin[0])
      {
         key1correct = true;
      }
   }
   else if (!key2correct)
   {
       if (key == pin[1])
       {
           key2correct = true;
       }
       else
       {
           key1correct = false;
       }
   }
   else if (!key3correct)
   {
       if (key == pin[2])
       {
           key3correct = true;
       }
       else
       {
           key1correct = false;
           key2correct = false;
       }
   }
   else
   {
       if (key == pin[3])
       {
           key4correct = true;
       }
       else
       {
           key1correct = false;
           key2correct = false;
           key3correct = false;
       }
   }

   if (key1correct && key2correct && key3correct && key4correct)
   {
       open_lock();
       key1correct = false;
       key2correct = false;
       key3correct = false;
       key4correct = false;
   }
}

Une version simplifiée et aplatie utilisant des drapeaux binaires (comme la réponse de User61852) ressemble à ceci:

void key_pressed(char key)
{
    if (!key1correct && key == pin[0])
    {
        key1correct = true;
        return;
    }

    if (key1correct && !key2correct && key == pin[1])
    {
        key2correct = true;
        return;
    }

    if (key1correct && key2correct && !key3correct && key == pin[2])
    {
        key3correct = true;
        return;
    }

    if (key1correct && key2correct && key3correct && key == pin[3])
    {
        open_lock();
        key1correct = false;
        key2correct = false;
        key3correct = false;
        return;
    }

    key1correct = false;
    key2correct = false;
    key3correct = false;
}

C'est beaucoup plus facile à lire, mais toujours dans ces deux solutions que vous avez des états comme key2correct && !key1correct qui sont complètement invalides et inutilisés. Alors que l'utilisation d'une variable d'état num_correct_keys au lieu des drapeaux binaires ressemblent à ceci:

void key_pressed(char key)
{
    if (key == pin[num_correct_keys])
        ++num_correct_keys;
    else
        num_correct_keys = 0;

    if (num_correct_keys == 4)
    {
        open_lock();
        num_correct_keys = 0;
    }
}

Certes, il s'agit d'un exemple artificiel, mais les gens écrivent souvent du code comme la première version dans des situations moins évidentes, répartis parfois sur plusieurs fichiers. L'utilisation d'une variable d'état simplifie souvent grandement le code, en particulier lorsque les indicateurs représentent une séquence d'événements.

4
Karl Bielefeldt

Je ne suis pas sûr que votre exemple est particulièrement bon pour cette question, mais il est possible de le condenser pour être beaucoup plus simple et préserver toujours la même logique:

if(fooSuccess && !barSuccess)
{
    if(mooSuccess)
    {
        // .....
    }
    else
    {
        // .....
    }
}
else if (!fooSuccess && barSuccess)
{
    if(mooSuccess)
    {
        // .....
    }
    else if(cowSuccess)
    {
        // .....
    }
    else 
    {
        // .....
    }
}

Notez comment il n'y a pas plus d'un niveau de if s. Pour nettoyer cela, j'ai pris les étapes suivantes:

  • Ne vérifiez jamais si une valeur booléenne est == true, != true, ou != false. De temps en temps, j'ai trouvé ça vaut la peine de vérifier s'il est == false Pour que cela apparaisse plus évident que c'est le cas attendu, mais même cela devrait être exceptionnel.
  • Si votre bloc de else ne consiste à rien d'autre qu'un bloc if/else bloque, vous pouvez la fusionner avec le parent else.
  • Si votre instruction if n'a qu'une entrée/variable, vous n'avez pas besoin de vérifier l'inverse pour le else.
2
Bobson