Question

Supposons que nous ayons une méthode qui accepte une valeur d’énumération. Après que cette méthode vérifie que la valeur est valide, switch es sur les valeurs possibles. La question est donc de savoir quelle est la méthode préférée pour gérer les valeurs inattendues après que la plage de valeurs ait été validée.

Par exemple:

enum Mood { Happy, Sad }

public void PrintMood(Mood mood)
{
    if (!Enum.IsDefined(typeof(Mood), mood))
    {
        throw new ArgumentOutOfRangeException("mood");
    }

    switch (mood)
    {
        case Happy: Console.WriteLine("I am happy"); break;
        case Sad:   Console.WriteLine("I am sad"); break;
        default: // what should we do here?
    }

Quelle est la méthode préférée pour traiter la casse default ?

  • Laissez un commentaire comme // ne peut jamais arriver
  • Debug.Fail () (ou Debug.Assert (false) )
  • lance la nouvelle NotImplementedException () (ou toute autre exception)
  • D'une autre manière, je n'y ai pas pensé
Était-ce utile?

La solution 2

Je préfère émettre une nouvelle exception NotImplementedException ("Humeur non gérée:" + humeur) . Le fait est que l'énumération peut changer dans le futur et que cette méthode peut ne pas être mise à jour en conséquence. Lancer une exception semble être la méthode la plus sûre.

Je n'aime pas la méthode Debug.Fail () , car la méthode peut faire partie d'une bibliothèque et les nouvelles valeurs peuvent ne pas être testées en mode débogage. D'autres applications utilisant cette bibliothèque peuvent être confrontées à un comportement d'exécution étrange dans ce cas, tandis que dans le cas d'une exception, l'erreur sera immédiatement connue.

Remarque: NotImplementedException existe sous commons.lang .

Autres conseils

Je suppose que la plupart des réponses ci-dessus sont valables, mais je ne suis pas sûr que toutes soient correctes.

La bonne réponse est que vous basculez très rarement dans une langue orientée objet, cela indique que vous ne le faites pas correctement. Dans ce cas, c’est une indication parfaite que votre classe Enum a des problèmes.

Vous devriez simplement appeler Console.WriteLine (mood.moodMessage ()) et définir moodMessage pour chacun des états.

Si un nouvel état est ajouté - Tout votre code doit s’adapter automatiquement, rien ne doit échouer, renvoyer une exception ou nécessiter des modifications.

Modifier: réponse au commentaire.

Dans votre exemple, être "Bon OO" la fonctionnalité du mode de fichier serait contrôlée par l'objet FileMode. Il pourrait contenir un objet délégué avec "ouvrir, lire, écrire ...". les opérations différentes pour chaque mode de fichier, donc File.open ("nom", FileMode.Create) peut être implémenté en tant que (désolé pour le manque de familiarité avec l'API):

open(String name, FileMode mode) {
    // May throw an exception if, for instance, mode is Open and file doesn't exist
    // May also create the file depending on Mode
    FileHandle fh = mode.getHandle(name);
    ... code to actually open fh here...
    // Let Truncate and append do their special handling
    mode.setPosition(fh);
}

Cela est beaucoup plus pratique que d'essayer de le faire avec des commutateurs ... (au fait, les méthodes seraient à la fois privées du paquet et éventuellement déléguées à des "classes" Mode ")

.

Lorsque OO est bien exécuté, chaque méthode ressemble à quelques lignes de code très compréhensible et simple - TROP simple. Vous avez toujours le sentiment qu’il ya un gros désordre dans le "Nuage de fromage". Tenir ensemble tous les petits objets de nacho, mais vous ne pouvez jamais le trouver - ce sont des nachos tout en bas ...

En Java, la méthode standard consiste à générer une AssertionError , pour deux raisons:

  1. Ceci garantit que même si assert est désactivé, une erreur est générée.
  2. Vous affirmez qu'il n'y a pas d'autres valeurs énumérées, donc AssertionError documente vos hypothèses mieux que NotImplementedException (que Java n'a de toute façon pas).

Mon opinion est que, puisqu'il s'agit d'une erreur de programmation, vous devez soit l'affirmer, soit émettre une exception RuntimException (Java, ou l'équivalent pour d'autres langages). J'ai ma propre UnhandledEnumException qui s'étend de la RuntimeException que j'utilise pour cela.

La bonne réponse du programme serait de mourir de manière à permettre au développeur de repérer facilement le problème. mmyers et JaredPar donnait de bonnes méthodes pour faire ça.

Pourquoi mourir? Cela semble si extrême!

La raison en est que si vous ne gérez pas correctement une valeur enum et que vous passez à travers , vous mettez votre programme dans un état inattendu. Une fois que vous êtes dans un état inattendu, qui sait ce qui se passe. Cela peut entraîner des données erronées, des erreurs plus difficiles à localiser, voire des vulnérabilités de sécurité.

En outre, si le programme meurt, il y a beaucoup plus de chances que vous le détectiez en assurance qualité et ainsi, il ne passe même pas par la porte.

Pour pratiquement toutes les instructions de commutateur de ma base de code, le cas par défaut suivant

switch( value ) { 
...
default:
  Contract.InvalidEnumValue(value);
}

La méthode lève une exception détaillant la valeur de l'énum au point où une erreur a été détectée.

public static void InvalidEnumValue<T>(T value) where T: struct
{
    ThrowIfFalse(typeof(T).IsEnum, "Expected an enum type");
    Violation("Invalid Enum value of Type {0} : {1}", new object[] { typeof(T).Name, value });
}

Pour C #, il est intéressant de savoir que Enum.IsDefined () est dangereux . Vous ne pouvez pas compter sur elle comme vous l'êtes. Obtenir quelque chose qui ne correspond pas aux valeurs attendues est un bon argument pour lancer une exception et mourir fort.

En Java, c'est différent parce que les énumérations sont des classes et non des entiers, vous ne pouvez donc pas obtenir de valeurs inattendues (à moins que l'énumération soit mise à jour et que l'instruction switch ne le soit pas), ce qui est l'une des principales raisons pour lesquelles je préfère nettement l'énumération Java. Vous devez également prendre en compte les valeurs nulles. Mais obtenir un cas non nul que vous ne reconnaissez pas est un bon argument pour lancer une exception également.

Vous pourriez avoir une trace de la valeur par défaut en appelant la valeur de l'énumération passée. Lancer des exceptions est correct, mais dans une application volumineuse, il y aura plusieurs endroits où votre code ne se souciera pas des autres valeurs de l'énum.
Donc, sauf si vous êtes sûr que le code a l'intention de gérer toutes les valeurs possibles de l'énum, ??vous devrez revenir plus tard et supprimer l'exception.

C’est l’une de ces questions qui prouve pourquoi le développement piloté par les tests est si important.

Dans ce cas, je choisirais une exception NotSupportedException car, littéralement, la valeur n'était pas gérée et donc non prise en charge. Une exception NotImplementedException donne plus l'idée de: "Ceci n'est pas encore fini" ;)

Le code de l'appelant doit pouvoir traiter une telle situation et des tests unitaires peuvent être créés pour tester facilement ce type de situation.

Appelez cela une opinion ou une préférence, mais l’idée derrière l’énumération est qu’elle représente la liste complète des valeurs possibles. Si une "valeur inattendue" est passé dans le code, alors l'énumération (ou le but derrière l'énumération) n'est pas à jour. Ma préférence personnelle est que chaque enum porte une assignation par défaut de Undefined. Étant donné que l'énumération est une liste définie, elle ne doit jamais être périmée avec votre code consommateur.

Pour ce qui est de savoir quoi faire si votre fonction obtient une valeur inattendue ou Indéfinie dans mon cas, une réponse générique ne semble pas possible. Pour moi, cela dépend du contexte de la raison pour laquelle on a évalué la valeur enum: est-ce une situation où l'exécution de code devrait être interrompue ou une valeur par défaut peut-elle être utilisée?

Il est de la responsabilité de la fonction appelante de fournir une entrée valide et implicitement tout ce qui ne figure pas dans l’énumération est invalide (le programmeur pragmatique semble l’impliquer). Cela dit, cela implique que chaque fois que vous modifiez votre enum, vous devez modifier TOUT code qui l'accepte en tant qu'entrée (et QUELQUE code qui le produit en sortie). Mais c'est probablement vrai de toute façon. Si vous avez une énumération qui change souvent, vous devriez probablement utiliser autre chose qu'une énumération, sachant que les énumérations sont normalement des entités au moment de la compilation.

J'essaie généralement de définir une valeur indéfinie (0):

enum Mood { Undefined = 0, Happy, Sad }

Ainsi, je peux toujours dire:

switch (mood)
{
    case Happy: Console.WriteLine("I am happy"); break;
    case Sad:   Console.WriteLine("I am sad"); break;
    case Undefined: // handle undefined case
    default: // at this point it is obvious that there is an unknown problem
             // -> throw -> die :-)
}

C’est au moins ce que je fais habituellement.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top