Utilisations appropriées des instructions de commutation de secours

14

Quand est-il approprié d'utiliser une instruction de basculement (classique)? Une telle utilisation est-elle recommandée et encouragée ou devrait-elle être évitée à tout prix?

shabunc
la source
Toutes les langues ne permettent pas d'interrompre les instructions de commutation.
Oded
@Oded, édité, ajouté un mot "classique". Toutes les langues ne permettent pas l'
échec
3
Si vous parlez de l'appareil de Duffs , c'est une chose.
Oded
6
@Oded: C'est la première chose à laquelle la plupart des gens pensent, mais ce n'est guère «approprié». Selon cela , Duff lui-même a déclaré: "Cela fournit certainement un argument dans la discussion si le commutateur doit échouer par défaut, mais je ne sais pas si l'argument est pour ou contre."
BlueRaja - Danny Pflughoeft

Réponses:

15

Voici un exemple où cela serait utile.

public Collection<LogItems> GetAllLogItems(Level level) {
    Collection<LogItems> result = new Collection<LogItems>();
    switch (level) {
        // Note: fall through here is INTENTIONAL
        case All:
        case Info:
             result.Add(GetItemsForLevel(Info));
        case Warning:
             result.Add(GetItemsForLevel(Warning));
        case Error:
             result.Add(GetItemsForLevel(Error));
        case Critical:
             result.Add(GetItemsForLevel(Critical));
        case None:
    }
    return result;
}

Ce genre de chose (où un cas inclut l'autre) est plutôt rare, je pense, c'est pourquoi certaines langues plus récentes ne permettent pas la substitution ou nécessitent une syntaxe spéciale pour cela.

configurateur
la source
13
Cet exemple, bien qu'intéressant, manque le // INTENTIONAL FALL THROUGH HEREcommentaire tout en majuscules requis .
Russell Borogove
Il est tout aussi facile d'écrire ce code avec échec en invoquant l' GetItemsForLevel(Info)appel GetItemsForLevel(Warning), etc.
Caleb
@RussellBorogove: Absolument correct.
configurateur
@Caleb: Dans ce cas, oui. Et si les appels étaient un peu plus compliqués? Il pourrait devenir un peu velu, tandis que la chute le rendrait simple. Je dois noter que je ne suis pas réellement en faveur de l'utilisation de Fall Through - je dis simplement que cela peut être utile dans certaines situations.
configurateur
Pas le meilleur exemple. Il y a un ordre des niveaux d'avertissement. En définissant cet ordre, cette méthode se réduit à une seule ligne d'éléments de filtrage de code lorsque le niveau d'élément est au moins égal au niveau souhaité.
Kevin Cline du
8

Je les utilise lorsque certaines fonctionnalités doivent être appliquées pour plusieurs valeurs. Par exemple, supposons que vous ayez un objet avec une propriété appelée operationCode. Si le code est égal à 1, 2, 3 ou 4, vous souhaitez démarrer OperationX (). Si c'est 5 ou 6, vous voulez démarrerOperationY () et 7 vous démarrezOperationZ (). Pourquoi avoir 7 cas complets avec des fonctionnalités et des pauses lorsque vous pouvez utiliser des raccourcis

Je pense que c'est complètement valable dans certaines situations, surtout si cela évite 100 déclarations if-else. =)

Yatrix
la source
4
Ce que vous décrivez est un switchavec plusieurs cases liés au même code. C'est différent d'une erreur, où l'exécution de l'un se casepoursuit dans le suivant en raison de l'absence d'un breakentre eux.
Blrfl
3
Cela n'a pas vraiment d'importance, la chute est simplement le manque d'une déclaration de rupture, donc elle "passe" au cas suivant.
Yatrix
Je reformulerais le scénario dans votre réponse pour refléter cela.
Blrfl
2
@Yatrix: Je ne suis pas d'accord. Il est très différent d'avoir des cas consécutifs exécutant tous exactement le même bloc de code que d'omettre une interruption. L'une est routinière, l'autre loin de là (ime) - et le potentiel de lecture erronée et de flux de contrôle involontaire est beaucoup plus grand.
quentin-star du
3
@qes oui, c'est différent, mais ce sont tous les deux des échecs. Il a demandé quand / si cela serait approprié. J'ai donné un exemple de quand cela arriverait. Ce n'était pas censé être un exemple tout compris. Selon la langue, avoir des déclarations avant une erreur peut ne pas fonctionner. Je ne pense pas que ce sera le cas avec C # stackoverflow.com/questions/174155/…
Yatrix
8

Je les ai utilisés de temps en temps, je pense que c'est toujours une utilisation appropriée - mais seulement lorsqu'ils sont inclus avec le commentaire approprié.

gbjbaanb
la source
7

Les cas de chute sont parfaitement bien. Je trouve souvent qu'une énumération est utilisée dans de nombreux endroits et que lorsque vous n'avez pas besoin de différencier certains cas, il est plus facile d'utiliser la logique de substitution.

Par exemple (notez les commentaires explicatifs):

public boolean isAvailable(Server server, HealthStatus health) {
  switch(health) {
    // Equivalent positive cases
    case HEALTHY:
    case UNDER_LOAD:
      return true;

    // Equivalent negative cases
    case FAULT_REPORTED:
    case UNKNOWN:
    case CANNOT_COMMUNICATE:
      return false;

    // Unknown enumeration!
    default:
      LOG.warn("Unknown enumeration " + health);
      return false;
  }
}

Je trouve ce type d'utilisation parfaitement acceptable.

Bringer128
la source
Notez qu'il y a une grande différence entre case X: case Y: doSomething()et case X: doSomething(); case Y: doAnotherThing(). Dans le premier, l'intention est assez explicite, tandis que dans le second, la chute peut être intentionnelle ou non. D'après mon expérience, le premier exemple ne déclenche jamais aucun avertissement dans les compilateurs / analyseurs de code, tandis que le second le fait. Personnellement, je n'appellerais le deuxième exemple que "tomber" - mais je ne suis pas sûr d'une définition "officielle".
Jens Bannmann
6

Ça dépend de:

  • votre préférence personnelle
  • les normes de codage de votre employeur
  • le niveau de risque impliqué

Les deux principaux problèmes associés au fait de laisser un cas passer au suivant sont les suivants:

  1. Cela rend votre code dépendant de l'ordre des déclarations de cas. Ce n'est pas le cas si vous ne vous trompez jamais, et cela ajoute un degré de complexité souvent indésirable.

  2. Il n'est pas évident que le code d'un cas inclut le code d'un ou de plusieurs cas ultérieurs.

Certains endroits interdisent explicitement de passer. Si vous ne travaillez pas dans un tel endroit, et si vous êtes à l'aise avec la pratique, et si la violation du code en question ne causera aucune souffrance réelle, ce ne sera peut-être pas la pire chose au monde. Si vous le faites, assurez-vous de mettre un commentaire accrocheur à proximité pour avertir ceux qui viendront plus tard (y compris l'avenir).

Caleb
la source
4

Voici un exemple rapide (certes incomplet (pas de traitement spécial de l'année bissextile)) de chute qui me simplifie la vie:

function get_julian_day (date) {
    int utc_date = date.getUTCDate();
    int utc_month = date.getUTCMonth();

    int julian_day = 0;

    switch (utc_month) {
        case 11: julian_day += 30;
        case 10: julian_day += 31;
        case 9:  julian_day += 30;
        case 8:  julian_day += 31;
        case 7:  julian_day += 31;
        case 6:  julian_day += 30;
        case 5:  julian_day += 31;
        case 4:  julian_day += 30;
        case 3:  julian_day += 31;
        case 2:  julian_day += 28;
        case 1:  julian_day += 31;
        default: break;
    }

    return julian_day + utc_date;
}
AaronDanielson
la source
5
Bien que ce soit un peu intelligent, le temps que vous économiserez de cette façon sera largement dépassé par le temps qu'il faudra aux autres pour comprendre ce que cela fait. En outre, vous pouvez obtenir de meilleures performances en supprimant l'aspect de transition, c'est-à-dire que 31 + 28 + 31 est toujours 90. Si vous allez faire cela, vous pourriez tout aussi bien mettre les totaux dans les cas car il n'y a que 11 à considérer.
JimmyJames
Vous avez raison, mon exemple est certainement artificiel :) Je dirai cependant qu'il est plus facile de repérer un bug caché lorsque le nombre de jours est énoncé comme ceci au lieu de pré-calculer tous les jours cumulés pour chaque cas de commutateur .
AaronDanielson
2
Je vous l'accorde mais faire cela dans une déclaration constante serait préférable par exemple FEB_START = 31; MAR_START = FEB_START + 28; etc. Je ne sais pas de quelle langue il s'agit mais dans beaucoup, il serait calculé au moment de la compilation. Le plus gros problème ici est que c'est un peu obtus. Pas tant que c'est une mauvaise idée, juste atypique. À moins qu'il y ait un très gros avantage, il est généralement préférable de s'en tenir aux idiomes courants.
JimmyJames
1
Excellente alternative, en utilisant des constantes pour les jours de début sur cet exemple. Quoi qu'il en soit, le point ici est qu'une somme cumulée est une grande utilité pour une erreur de commutation. Je suis d'accord à 100% sur l'utilisation d'expressions idiomatiques communes, mais la nature de cette question est de s'aventurer dans les caractéristiques les plus ésotériques où les expressions idiomatiques communes sont difficiles à trouver.
AaronDanielson
3

Si je ressens le besoin de passer d'un cas à l'autre (rare, certes), je préfère être très explicite et goto case, bien sûr, cela suppose que votre langue le supporte.

Parce que tomber à travers est si rare et très facile à ignorer lors de la lecture de code, je pense qu'il est approprié d'être explicite - et un goto, même s'il s'agit d'un cas, devrait se démarquer comme un pouce endolori.

Cela permet également d'éviter les bogues qui peuvent survenir lors de la réorganisation des instructions de cas.

quentin-starin
la source