2010-06-28 4 views
2

Disons que vous avez une méthode très longue, comme ceci:déclarations de retour lorsque vous faites Extrait Méthode

int monster() 
{ 
    int rc = 0; 

    // some statements ... 

    if (dragonSlayer.on_vacation()) { 
     cout << "We are screwed!\n"; 
     if (callTheKing() == true) 
      return 1; 
     else 
      return 2; 
    } else { 
     cout << "We are saved!\n"; 
     slayTheDragon(); 
    } 

    // rest of long method... 

    return rc; 
} 

et je travaille sur squelettisation le code. Je veux extraire la partie de dragon à terrassant

int handleDragon() { 
    if (dragonSlayer.on_vacation()) { 
     cout << "We are screwed!\n"; 
     if (callTheKing() == true) 
      return 1; 
     else 
      return 2; 
    } else { 
     cout << "We are saved!\n"; 
     slayTheDragon(); 
    } 

    return 0; // ? 
} 

et remplacer le code monstre() avec un appel à handleDragon().

Mais il y a un problème. Il y a une déclaration de retour au milieu de cette partie. Si je garde la partie où le code retour de handleDragon() est géré, il gardera la litière dans la grosse méthode. En plus des exceptions, existe-t-il un moyen élégant et sûr de refactoriser ce morceau de code par la méthode monstre? Comment ces types de situations devraient-ils être traités?

+1

Si vous allez comparer les booléens avec 'true' comme ça, alors notez que' callTheKing() == true' est aussi un booléen. Cela devrait donc être '(callTheKing() == true) == true'. –

+1

Plus sérieusement, si vous utilisez des valeurs de retour plutôt que des exceptions, vous avez besoin d'un schéma cohérent. Ici vous avez une fonction retournant 'int' (avec zéro pour le succès) et d'autres (apparemment) retournant' bool' (avec zéro pour l'échec). Vous demandez simplement quelqu'un pour mélanger les deux régimes. –

Répondre

2

Retour 0 de la méthode handleDragon si le dragon slayer est disponible:

int handleDragon() { 
    if (dragonSlayer.on_vacation()) { 
     cout << "We are screwed!\n"; 
     if (callTheKing() == true) 
      return 1; 
     else 
      return 2; 
    } else { 
     cout << "We are saved!\n"; 
     slayTheDragon(); 
     return 0; 
    } 
} 

Puis retour dans la méthode monster, si la valeur de retour était supérieur à zéro, retourner cette valeur, porter autrement sur:

// some statements ... 

int handleDragonResult = handleDragon(); 
if (handleDragonResult > 0) { 
    return handleDragonResult; 
} 

// rest of long method... 

Vous devez également documenter la méthode handleDragon pour expliquer la valeur renvoyée.

0

Ne pourriez-vous faire ceci:

int handleDragon() { 
    int rc = 0; 

    if (dragonSlayer.on_vacation()) { 
     cout << "We are screwed!\n"; 
     if (callTheKing() == true) 
      rc = 1; 
     else 
      rc = 2; 
    } else { 
     cout << "We are saved!\n"; 
     slayTheDragon(); 
    } 

    return rc; 
} 

puis:

int monster() 
{ 
    int rc = 0; 

    // some statements ... 

    rc = handleDragon(); 

    // rest of long method... 

    return rc; 
} 

ou si vous voulez faire quelque chose avec le code de retour:

int monster() 
{ 
    int rc = 0; 

    // some statements ... 

    int handleDragonReturnCode = handleDragon(); 

    if(handleDragonReturnCode == 0) { 
     // do something 
    } 

    else { 
     // do something else 
    } 

    // rest of long method... 

    return rc; 
} 

Est-ce que tu veux? Sur une note générale, évitez d'utiliser nombres magiques comme 1 et 2 pour vos codes de retour. Utilisez les constantes, #define ou enum. Concernant return, essayez d'avoir un point de sortie de votre fonction. Comme vous l'avez découvert, avoir plusieurs instructions return peut rendre le refactoring difficile (ainsi que la compréhension de la logique, sauf si c'est vraiment simple).

+3

_Single entry, single exit_ est un vestige de l'ère sombre de la programmation pure structurée et des fonctions longues, qui se déversent sur plusieurs pages sur le moniteur. S'y tenir vous laisse croire que vous pouvez supposer qu'une fonction ne reviendra pas prématurément - ce qui n'est jamais vrai, car en C++ nous avons des exceptions. Rendez vos fonctions courtes et exprimez le flux de contrôle à travers des instructions de flux de contrôle plutôt que de manipuler une variable de retour, en masquant l'algorithme réel sous les instructions 'if' qui testent toutes l'état de la variable de retour. – sbi

+0

Je ne dirais pas que c'est un "restes stupides". Je dirais que c'est l'exception plutôt que la règle. La raison pour laquelle je dis cela, c'est parce qu'il est facile d'abuser de plusieurs retours et je l'ai vu beaucoup trop de fois. Par conséquent, la note d'avertissement. Je suis d'accord avec ce que vous dites en général en ce qui concerne le contrôle de flux. –

1
enum DragonHandled { DHSuccess, DHKing, DHNoKing }; 

inline DragonHandled askForKing() 
{ 
    if (callTheKing()) 
     return DHKing; 
    else 
     return DHNoKing; 
} 

DragonHandled handleDragon() 
{ 
    if (dragonSlayer.on_vacation()) { 
     cout << "We are screwed!\n"; 
     return askForKing(); 
    } 
    cout << "We are saved!\n"; 
    slayTheDragon(); 
    return DHSuccess; 
} 

int monster() 
{ 
    some_statements(...); 

    DragonHandled handled = handleDragon(); 
    if(handled != DHSuccess) 
     return handled; // enum to int is an implicit cast 

    return more_statements(...); 
} 
  • Sauf pour une fonction qui renvoie un nombre signé réelle, je ne retournerais pas int. Si le résultat a une signification, définissez ce sens correctement (c'est-à-dire: enum).
  • Une fonction fait quelque chose, et quoi qu'il fasse, devrait être visible dans son nom.Il devrait y avoir un verbe au nom d'une fonction (handledragon(), callTheKing()). monsters n'est pas un verbe, ce n'est pas quelque chose que vous pouvez faire. Si je vois un identifiant monsters, je pense que c'est un conteneur pour les monstres.
  • Vérification if(x == true) est tout simplement inutile, puisque if(x) est plus simple, plus simple et tout aussi vrai.
+0

"monstre" était simplement ma façon de souligner que c'est une méthode monstre, ce n'est pas une méthode réelle pour mon code. Je viens de créer du code imaginaire pour énoncer un problème que j'ai rencontré. – Michael

+0

Il est assez commun de prononcer des mots comme "monstre" autour de moi. –

+0

@Mike: Je suis un non-natif, donc je peux me tromper ici. Je n'aurais pas pensé que tu pouvais transformer cela en verbe. Mea culpa ... – sbi

0

La question portait sur la stratégie, donc je pense que la réponse de Richard Fearn est bonne.

Pour en faire un modèle de refactoring il ressemblerait à quelque chose comme:

Contexte: Une section au milieu d'une méthode plus grande est à extraire.

Problème: La section contient des instructions de retour.

Solution:

  1. Extrait du code à une nouvelle méthode de retour du même type que la méthode plus grande.
  2. Trouvez une valeur de ce type qui ne signifie rien. Appelez cette valeur CONTINUE.
  3. Ajoutez une instruction à la fin de la nouvelle méthode qui renvoie CONTINUE.
  4. Dans la méthode plus grande, testez la valeur de retour de la nouvelle méthode pour CONTINUER. Si ce n'est pas le cas, retournez cette valeur.

Ceci serait l'approche principale. Comme prochaine étape, vous pouvez refactoriser les valeurs de retour de la nouvelle méthode à quelque chose de plus significatif (comme dans la réponse de sbi). Et vous devrez trouver un moyen de gérer le cas où le type de retour n'est pas un type scalaire ou simple, retournant un objet NULL ou un tel.

Questions connexes