Refactoring des instructions Switch et y a-t-il une réelle utilité pour les instructions Switch?

28

Je lisais cet article et je me demandais: supprimons-nous toutes les instructions de commutateur en les remplaçant par un dictionnaire ou une usine afin qu'il n'y ait aucune instruction de commutateur dans mes projets.

Quelque chose ne s'additionnait pas vraiment.

La question est de savoir si les instructions switch ont une utilité réelle ou allons-nous les remplacer par un dictionnaire ou une méthode d'usine (en utilisant une méthode d'usine, bien sûr, il y aura une utilisation minimale des instructions switch pour créer les objets en utilisant l'usine ... mais c'est à peu près tout).

Kanini
la source
10
En supposant que vous implémentez une usine, comment déciderez-vous quel type d'objet créer?
CodeART
@CodeWorks: Je vais bien sûr avoir des conditions quelque part, décider quelle implémentation concrète utiliser.
Kanini
@CodeWorks: Avec un constructeur virtuel, évidemment. (Et si vous ne pouvez pas implémenter un modèle d'usine de cette façon, vous avez besoin d'un meilleur langage.)
Mason Wheeler

Réponses:

44

Les switchdéclarations et le polymorphisme ont tous deux leur utilité. Notez cependant qu'une troisième option existe également (dans les langages qui prennent en charge les pointeurs de fonction / lambdas et les fonctions d'ordre supérieur): mapper les identificateurs en question aux fonctions de gestionnaire. Ceci est disponible par exemple en C qui n'est pas un langage OO, et C # qui est *, mais pas (encore) en Java qui est aussi OO *.

Dans certains langages procéduraux (n'ayant ni polymorphisme ni fonctions d'ordre supérieur) switch/ les if-elsedéclarations étaient le seul moyen de résoudre une classe de problèmes. De nombreux développeurs, habitués à cette façon de penser, ont continué à utiliser switchmême dans les langages OO, où le polymorphisme est souvent une meilleure solution. C'est pourquoi il est souvent recommandé d'éviter les switchdéclarations / refactor en faveur du polymorphisme.

Quoi qu'il en soit, la meilleure solution dépend toujours de la casse. La question est: quelle option vous donne un code plus propre, plus concis et plus facile à gérer à long terme?

Les instructions de commutation peuvent souvent devenir lourdes, avec des dizaines de cas, ce qui rend leur maintenance difficile. Comme vous devez les conserver dans une seule fonction, cette fonction peut devenir énorme. Si tel est le cas, vous devriez envisager une refactorisation vers une solution basée sur une carte et / ou polymorphe.

Si le même switchcommence à apparaître à plusieurs endroits, le polymorphisme est probablement la meilleure option pour unifier tous ces cas et simplifier le code. Surtout si d'autres cas devraient être ajoutés à l'avenir; plus vous avez besoin de mises à jour à chaque fois, plus il y a de possibilités d'erreurs. Cependant, souvent les gestionnaires de cas individuels sont si simples, ou il y en a tellement, ou ils sont tellement interdépendants, que les refactoriser dans une hiérarchie de classes polymorphes complète est exagéré, ou entraîne beaucoup de code dupliqué et / ou emmêlé, difficile de maintenir la hiérarchie des classes. Dans ce cas, il peut être plus simple d'utiliser à la place des fonctions / lambdas (si votre langue le permet).

Cependant, si vous en avez un switchdans un seul endroit, avec seulement quelques cas faisant quelque chose de simple, ce pourrait bien être la meilleure solution pour le laisser tel quel.

* J'utilise le terme "OO" de manière lâche ici; Je ne suis pas intéressé par les débats conceptuels sur ce qui est «réel» ou «pur» OO.

Péter Török
la source
4
+1. En outre, ces recommandations ont tendance à être formulées, " préférez le polymorphisme pour changer" et c'est un bon choix de mot, tout à fait conforme à cette réponse. Il reconnaît qu'il existe des circonstances où un codeur responsable pourrait faire l'autre choix.
Carl Manaster
1
Et il y a des moments où vous voulez l'approche du changement même s'il y en a des millions dans votre code. J'ai en tête un morceau de code qui génère un labyrinthe en 3D. L'utilisation de la mémoire augmenterait considérablement si les cellules d'un octet du tableau étaient remplacées par des classes.
Loren Pechtel
14

C'est là que je redeviens dinosaure ...

Les instructions Switch ne sont pas mauvaises en soi, c'est l'utilisation qui en est faite qui est en cause.

La plus évidente est la "même" instruction de commutateur répétée maintes et maintes fois dans votre code qui est mauvaise (là-bas, fait cela, ferait tout son possible pour ne pas recommencer) - et c'est ce dernier cas que vous pourrez peut-être traiter avec l'utilisation du polymorphisme. Il y a généralement quelque chose d'assez horrible dans les cas imbriqués aussi (j'avais l'habitude d'avoir un monstre absolu - je ne sais pas vraiment comment je le gère maintenant autre que "mieux").

Dictionnaire comme commutateur Je trouve plus difficile - fondamentalement oui si votre commutateur couvre 100% des cas, mais là où vous voulez avoir des cas par défaut ou sans action, cela commence à devenir un peu plus intéressant.

Je pense que c'est une question d'éviter la répétition et de nous assurer que nous composons nos graphiques d'objets aux bons endroits.

Mais il y a aussi l'argument de compréhension (maintenabilité) et cela coupe dans les deux sens - une fois que vous comprenez comment tout cela fonctionne (le modèle et l'application dans laquelle il est implémenté), c'est facile ... mais si vous arrivez à une seule ligne de code où vous devez ajouter quelque chose de nouveau, vous devez ensuite sauter partout pour déterminer ce que vous devez ajouter / modifier.

Pour autant que nos environnements de développement soient massivement capables, je pense toujours qu'il est souhaitable de pouvoir comprendre le code (comme s'il était) imprimé sur du papier - pouvez-vous suivre le code avec votre doigt? J'accepte qu'en fait non, avec beaucoup de bonnes pratiques aujourd'hui, vous ne pouvez pas et pour de bonnes raisons, mais cela signifie qu'il est plus difficile de se familiariser avec le code (ou peut-être que je suis juste vieux ...)

Murph
la source
4
J'ai eu un enseignant qui a dit que l'idéal serait que le code soit écrit "pour que quelqu'un qui ne connaît rien à la programmation (comme peut-être votre mère) puisse le comprendre". Malheureusement, c'est un idéal, mais nous devrions peut-être quand même inclure nos mamans dans les revues de code!
Michael K
+1 pour "mais si vous arrivez à une seule ligne de code où vous devez ajouter quelque chose de nouveau, vous devez alors sauter partout pour déterminer ce que vous devez ajouter / modifier"
quick_now
8

Switch-statement vs subtype-polymorphism est un vieux problème et est souvent mentionné dans la communauté FP dans les discussions sur le problème d'expression .

Fondamentalement, nous avons des types (classes) et des fonctions (méthodes). Comment codons-nous des choses pour qu'il soit facile d'ajouter de nouveaux types ou de nouvelles méthodes?

Si vous programmez dans le style OO, il est difficile d'ajouter une nouvelle méthode (car cela signifierait une refactorisation de toutes les classes existantes), mais il est très facile d'ajouter de nouvelles classes qui utilisent les mêmes méthodes qu'auparavant.

D'un autre côté, si vous utilisez une instruction switch (ou son équivalent OO, le modèle Observer), il est très facile d'ajouter de nouvelles fonctions, mais il est difficile d'ajouter de nouveaux cas / classes.

Il n'est pas facile d'avoir une bonne extensibilité dans les deux sens, donc lors de l'écriture de votre code, déterminez s'il faut utiliser le polymorphisme ou basculer les instructions en fonction de la direction dans laquelle vous êtes le plus susceptible d'étendre ce dernier.

hugomg
la source
4
pouvons-nous nous débarrasser de toutes les instructions switch en les remplaçant par un dictionnaire ou une fabrique afin qu'il n'y ait aucune instruction switch du tout dans mes projets.

Non. De tels absolus sont rarement une bonne idée.

À de nombreux endroits, une répartition dictionnaire / recherche / usine / polymorphe fournira une meilleure conception qu'une instruction switch, mais vous devez toujours remplir le dictionnaire. Dans certains cas, cela obscurcira ce qui se passe réellement et le simple fait d'avoir l'instruction switch en ligne est plus lisible et maintenable.

Telastyn
la source
Et vraiment, si vous dérouliez l'usine et le dictionnaire et la recherche, cela se révélerait être essentiellement une instruction switch: si tableau [hachage (recherche)], alors appelez tableau [hachage (recherche)]. Bien qu'il soit extensible à l'exécution, les commutateurs compilés ne le sont pas.
Zan Lynx
@ZanLynx, l'opposé complet est vrai, au moins avec C #. Si vous regardez l'IL produit pour une instruction switch non triviale, vous verrez que le compilateur la transforme en dictionnaire. Car c'est plus rapide.
David Arno
@DavidArno: "l'opposé complet"? La façon dont je l'ai lu, nous avons dit exactement la même chose. Comment peut-il être opposé?
Zan Lynx
2

Étant donné que cela est indépendant du langage, le code de transition fonctionne mieux avec les switchinstructions:

switch(something) {
   case 1:
      foo();
   case 2:
      bar();
      baz();
      break;

   case 3:
      bang();
   default:
      bizzap();
      break;
}

Équivalent à if, avec un cas par défaut très gênant. Et gardez à l'esprit que plus il y a de retombées, plus la liste de conditions s'allonge:

if (1 == something) {
   foo();
}
if (1 == something || 2 == something) {
   bar();
   baz();
}
if (3 == something) {
   bang();
}
if (1 != something && 2 != something) {
   bizzap();
}

(Cependant, compte tenu de ce que disent certaines des autres réponses, j'ai l'impression d'avoir raté le point de la question ...)

Izkata
la source
Je considérerais l'utilisation de déclarations de substitution dans a switchcomme étant au même niveau que a goto. Si l'algorithme à exécuter nécessite des flux de contrôle qui correspondent à des constructions de programmation structurées, il faut utiliser de telles constructions, mais si l'algorithme ne correspond pas à de telles constructions, l'utilisation gotopeut être meilleure que d'essayer d'ajouter des indicateurs ou une autre logique de contrôle pour s'adapter à d'autres structures de contrôle .
supercat
1

Les instructions Switch car la différenciation des cas ont une réelle utilité. Les langages de programmation fonctionnels utilisent quelque chose appelé correspondance de modèle qui vous permet de définir des fonctions différemment selon l'entrée. Cependant, dans les langages orientés objet, le même objectif peut être atteint plus élégamment en utilisant le polymorphisme. Vous appelez simplement la méthode et selon le type réel de l'objet, l'implémentation correspondante de la méthode sera exécutée. C'est la raison derrière l'idée, que les instructions switch sont une odeur de code. Cependant, même dans les langages OO, vous pourriez les trouver utiles pour implémenter le modèle d'usine abstrait.

tl; dr - ils sont utiles, mais vous pouvez souvent faire mieux.

écharpe
la source
2
Je sens une certaine distorsion - pourquoi le polymorphisme est-il plus élégant que la correspondance de motifs? Et qu'est-ce qui vous fait penser que le polymorphisme n'existe pas dans FP?
tdammers
@tdammers Tout d'abord, je ne voulais pas laisser entendre que le polymorphisme n'existe pas dans FP. Haskell prend en charge le polymorphisme ad hoc et paramétrique. La correspondance de motifs est logique pour un type de données algébrique. Mais pour les modules externes, cela nécessite d'exposer les constructeurs. Il est clair que cela rompt l'encapsulation d'un type de données abstrait (réalisé avec des types de données algébriques). C'est pourquoi je pense que le polymorphisme est plus élégant (et possible en FP).
scarfridge
Vous oubliez le polymorphisme à travers les classes et les instances.
tdammers
Avez-vous déjà entendu parler du problème d'expression ? L'OO et le FP sont meilleurs dans des situations différentes, si vous arrêtez d'y penser.
hugomg
@tdammers De quel type de polymorphisme pensez-vous que je parlais lorsque j'ai mentionné le polymorphisme ad hoc? Le polymorphisme ad hoc est réalisé dans Haskell à travers des classes de types. Comment pouvez-vous dire que je l'ai oublié? Tout simplement pas vrai.
scarfridge