web-dev-qa-db-fra.com

Écriture de commutation - Manipulation du cas par défaut quand il est impossible d'atteindre

Si j'utilise une instruction de commutation pour gérer les valeurs d'un énumé (qui appartient à ma classe) et que j'ai une affaire pour chaque valeur possible - est-ce que cela vaut la peine d'ajouter du code pour gérer le cas "Par défaut"?

enum MyEnum
{
    MyFoo,
    MyBar,
    MyBat
}

MyEnum myEnum = GetMyEnum();
switch (myEnum)
{
    case MyFoo:
        DoFoo();
        break;
    case MyBar:
        DoBar();
        break;
    case MyBat:
        DoBat();
        break;
    default:
        Log("Unexpected value");
        throw new ArgumentException() 
}

Je ne pense pas que ce soit parce que ce code ne peut jamais être atteint (même avec des tests unitaires). Mon collègue n'est pas d'accord et pense que cela nous protège contre des comportements inattendus causés par de nouvelles valeurs ajoutées au myenum.

Qu'est-ce que vous dites, communauté?

16
s d

Y compris le cas par défaut ne change pas la façon dont votre code fonctionne, mais cela rend votre code plus maintenu. En faisant la pause du code de manière évidente (logez un message et lancez une exception), vous inclut une grande flèche rouge pour le stagiaire que votre entreprise embauche l'été prochain pour ajouter quelques fonctionnalités. La flèche dit: "Hé, toi! Oui, je te parle! Si vous allez ajouter une autre valeur à l'énum, ​​vous feriez mieux d'ajouter un cas ici aussi." Cet effort supplémentaire pourrait ajouter quelques octets au programme compilé, qui est quelque chose à considérer. Mais cela sauvera également quelqu'un (peut-être même l'avenir que vous) quelque part entre une heure et une journée de gratte-tête improductive.


Mise à jour : La situation décrite ci-dessus, c'est-à-dire la protection contre les valeurs ajoutées à une énumération à un moment donné, peut également être capturée par le compilateur. CLANG (et GCC, je pense) passera par défaut un avertissement si vous allumez un type énuméré mais que vous n'avez pas de cas qui couvre toutes les valeurs possibles de l'énumération. Ainsi, par exemple, si vous supprimez le boîtier default de votre commutateur et ajoutez une nouvelle valeur MyBaz à l'énumération, vous obtiendrez un avertissement indiquant:

Enumeration value 'MyBaz' not handled in switch

Laisser le compilateur à détecter les cas non couverts est qu'il élimine largement la nécessité de ce cas inaccessible default cas qui a inspiré votre question en premier lieu.

35
Caleb

Je ne faisais que parler avec un collègue à ce sujet ce matin aussi - c'est vraiment malheureux, mais je pense que la gestion de la valeur par défaut est requise pour la sécurité, pour deux raisons:

Premièrement, comme votre collègue mentionne, il éprouve le code contre de nouvelles valeurs ajoutées à l'ENUM. Cela peut sembler ou non être une possibilité, mais c'est toujours là.

Plus important encore, en fonction de la langue/du compilateur, il peut être possible d'avoir des valeurs qui ne sont pas membres de l'ENUM dans votre variable commutée. Par exemple, en C #:

MyEnum myEnum = (MyEnum) 3; // This could come from anywhere, maybe parsed from text?
// ... code goes on for a while

switch ( myEnum )
{
    case MyEnum.A:
        // ... handle A case
        break;
    case MyEnum.B:
        // ... handle B case
        break;
}

// ... code that expects either A or B to have happened

En ajoutant le simple case default: et jetant une exception, vous vous êtes protégé contre ce cas étrange où "rien" ne se passe comme "quelque chose" aurait dû se passer.

Malheureusement, tout le temps, j'écris à tout moment une déclaration de commutation, c'est parce que je vérifie les cas d'une énumération. Je souhaite vraiment que le comportement "à jeter par défaut" puisse être appliqué par la langue elle-même (au moins en ajoutant un mot-clé).

5
Chris Phillips

Ajout d'un cas par défaut, même si vous ne vous attendez jamais à l'atteindre, cela peut être une bonne chose. Il ne fera que déboguer beaucoup plus facilement si votre code jette une exception "Cela n'aurait pas dû arriver" immédiatement plutôt que plus tard dans le programme, trompant une mystérieuse exception ou retournant des résultats inattendus sans erreur.

3
Ryathal

Je dis:

Essayez d'ajouter un autre type à MyEnum. Puis changez cette ligne:

MyEnum myEnum = GetMyEnum();

à

MyEnum myEnum = SomethingElse;

Exécutez ensuite votre code avec le boîtier par défaut et sans l'affaire par défaut. Quel comportement préférez-vous?

Avoir le cas par défaut peut également être utile pour piéger les valeurs NULL et prévenir NullPointerExceptions.