web-dev-qa-db-fra.com

Meilleures pratiques pour l'utilisation et la persistance des énumérations

J'ai vu plusieurs questions/discussions ici sur la meilleure façon de gérer et de conserver les valeurs de type énumération (par exemple données persistantes adaptées aux énumérations , comment conserver une énumération à l'aide de NHibernate ), et j'aimerais savoir quel est le consulat général.

En particulier:

  • Comment ces valeurs doivent-elles être gérées dans le code?
  • Comment devraient-ils être conservés dans une base de données (sous forme de texte/sous forme de nombre)?
  • Quels sont les compromis des différentes solutions?

Remarque: J'ai déplacé les explications initialement incluses dans cette question vers une réponse.

75
sleske

J'ai essayé de résumer ma compréhension. N'hésitez pas à le modifier si vous avez des corrections. Alors ça y va:

Dans le code

Dans le code, les énumérations doivent être gérées en utilisant le type d'énumération natif du langage (au moins dans Java et C #), ou en utilisant quelque chose comme "modèle d'énumération typesafe" L'utilisation de constantes simples (Entier ou similaire) est déconseillée, car vous perdez la sécurité du type (et il est difficile de comprendre quelles valeurs sont légales pour une méthode, par exemple).

Le choix entre ces deux dépend de la quantité de fonctionnalités supplémentaires à attacher à l'énumération:

  • Si vous voulez mettre beaucoup de fonctionnalités dans l'énumération (ce qui est bien, car vous évitez de basculer () dessus tout le temps), une classe est généralement plus appropriée.
  • En revanche, pour les valeurs de type énumération simples, l'énumération du langage est généralement plus claire.

En particulier, au moins dans Java une énumération ne peut pas hériter d'une autre classe, donc si vous avez plusieurs énumérations avec un comportement similaire que vous aimeriez mettre dans une superclasse, vous ne pouvez pas utiliser les énumérations de Java.

Énumérations persistantes

Pour conserver les énumérations, chaque valeur d'énumération doit se voir attribuer un ID unique. Cela peut être soit un entier, soit une chaîne courte. Une chaîne courte est préférée, car elle peut être mnémonique (permet aux DBA, etc. de comprendre plus facilement les données brutes dans la base de données).

  • Dans le logiciel, chaque énumération devrait alors avoir des fonctions de mappage pour convertir entre l'énumération (pour une utilisation à l'intérieur du logiciel) et la valeur ID (pour la persistance). Certains frameworks (par exemple (N) Hibernate) ont un support limité pour le faire automatiquement. Sinon, vous devez le mettre dans le type/classe enum.
  • La base de données devrait (idéalement) contenir un tableau pour chaque énumération énumérant les valeurs légales. Une colonne serait l'ID (voir ci-dessus), qui est le PK. Des colonnes supplémentaires pourraient être utiles, par exemple pour une description. Toutes les colonnes de table qui contiendront des valeurs de cette énumération peuvent alors utiliser cette "table d'énumération" comme FK. Cela garantit que les valeurs d'énumération incorrectes ne peuvent jamais être persistées et permet à la base de données de "se débrouiller seule".

Un problème avec cette approche est que la liste des valeurs d'énumération légales existe à deux endroits (code et base de données). C'est difficile à éviter et donc souvent considéré comme acceptable, mais il existe deux alternatives:

  • Gardez uniquement la liste des valeurs dans la base de données, générez le type d'énumération au moment de la construction. Élégant, mais signifie qu'une connexion DB est requise pour l'exécution d'une build, ce qui semble problématique.
  • Définissez la liste de valeurs dans le code pour faire autorité. Vérifiez les valeurs de la base de données au moment de l'exécution (généralement au démarrage), réclamez/abandonnez en cas de non-concordance.
7
sleske

Je suis d'accord avec beaucoup de ce que vous dites. Une chose que j'aimerais ajouter, cependant, à propos de la persistance des énumérations: je ne crois pas que la génération des énumérations au moment de la construction à partir des valeurs de la base de données soit acceptable, mais je pense également que la vérification de l'exécution n'est pas une bonne solution. . Je définirais un troisième moyen: avoir un test unitaire qui vérifiera les valeurs de l'énumération par rapport à la base de données. Cela empêche la divergence "occasionnelle" et évite la surcharge de vérification des énumérations par rapport à la base de données chaque fois que le code est exécuté.

18
Paul Sonier

L'article initial me semble bien. Pourtant, sur la base des commentaires, il semble que certains commentaires concernant Java pourraient clarifier peu de choses.

Enum type in Java est une classe par définition, mais de nombreux programmeurs ont tendance à l'oublier, car ils la relient plutôt à "une liste de valeurs autorisées" comme dans d'autres langages. C'est plus que cela .

Ainsi, pour éviter ces instructions switch, il peut être raisonnable de mettre du code et des méthodes supplémentaires dans la classe enum. Il n'est presque jamais nécessaire de créer une "classe réelle semblable à une énumération".

Considérez également le point de la documentation - voulez-vous documenter la signification réelle de votre énumération dans la base de données? Dans le code source reflétant les valeurs (votre type d'énumération) ou dans une documentation externe? Personnellement, je préfère le code source.

Si vous souhaitez présenter les valeurs d'énumération sous forme d'entiers dans la base de données en raison de la vitesse ou pour une raison quelconque, ce mappage doit également résider dans l'énumération Java. Vous obtiendrez le mappage de nom de chaîne par défaut, et Je me suis contenté de cela. Il y a un nombre ordinal associé à chaque valeur d'énumération, mais l'utiliser directement comme mappage entre le code et la base de données n'est pas très clair, car ce nombre ordinal changera si quelqu'un réordonne les valeurs dans le code source. Ou ajoute des valeurs d'énumération supplémentaires entre les valeurs existantes. Ou supprime une certaine valeur.

(Bien sûr, si quelqu'un change le nom de l'énumération dans le code source, le mappage de chaîne par défaut devient aigre aussi, mais cela est moins susceptible de se produire accidentellement. Et vous pouvez plus facilement vous protéger contre cela si nécessaire en mettant une vérification à l'exécution et vérifiez les contraintes dans la base de données comme déjà suggéré ici.)

13
lokori

Dans la gestion du code pour C #, vous avez manqué de définir la suppression de la valeur 0. Je déclare presque sans faute toujours ma première valeur comme:

public enum SomeEnum
{
    None = 0,
}

Afin de servir de valeur nulle. Parce que le type de support est un entier et un entier par défaut à 0, il est donc extrêmement utile dans de nombreux endroits pour savoir si une énumération a été définie par programme ou non.

6
Quibblesome

Java ou C # doit toujours utiliser des énumérations dans le code. Avertissement: Mon parcours est C #.

Si la valeur doit être conservée dans une base de données, les valeurs intégrales de chaque membre d'énumération doivent être explicitement définies afin qu'un changement ultérieur de code ne modifie pas accidentellement les valeurs d'énumération traduites et donc le comportement de l'application.

Les valeurs doivent toujours être conservées dans une base de données en tant que valeurs intégrales, pour se protéger contre le refactoring de nom enum. Conservez la documentation sur chaque énumération dans un wiki et ajoutez un commentaire au champ de la base de données pointant vers la page wiki documentant le type. Ajoutez également de la documentation XML au type enum contenant un lien vers l'entrée wiki afin qu'elle soit disponible via Intellisense.

Si vous utilisez un outil pour générer du code CRUD, il doit être capable de définir un type d'énumération à utiliser pour une colonne afin que les objets de code générés utilisent toujours des membres énumérés.

Si une logique personnalisée doit être appliquée pour un membre d'énumération, vous avez quelques options:

  • Si vous avez un enum MyEnum, créez une classe statique MyEnumInfo qui propose des méthodes utilitaires pour découvrir des informations supplémentaires sur le membre enum, par des instructions switch ou par tout autre moyen nécessaire. Ajouter "Info" à la fin du nom de l'énumération dans le nom de classe garantit qu'ils seront côte à côte dans IntelliSense.
  • Décorez les membres d'énumération avec des attributs pour spécifier des paramètres supplémentaires. Par exemple, nous avons développé un contrôle EnumDropDown qui crée une liste déroulante ASP.NET remplie de valeurs d'énumération, et un EnumDisplayAttribute spécifie le texte d'affichage bien formaté à utiliser pour chaque membre.

Je n'ai pas essayé cela, mais avec SQL Server 2005 ou version ultérieure, vous pouvez théoriquement enregistrer du code C # avec la base de données qui contiendrait des informations d'énumération et la possibilité de convertir des valeurs en énumérations pour les utiliser dans des vues ou d'autres constructions, créant une méthode de traduction de la données d'une manière plus facile à utiliser pour les administrateurs de base de données.

5
David Boike

Eh bien, d'après mon expérience, en utilisant des énumérations pour autre chose que pour passer des options (en tant que drapeaux) à un appel de méthode immédiat, il en résulte switch- à un moment donné.

  • Si vous allez utiliser l'énumération partout dans votre code, vous pourriez vous retrouver avec un code qui n'est pas si facile à maintenir (la fameuse instruction switch)
  • L'extension des énumérations est une douleur. Vous ajoutez un nouvel élément enum et finissez par parcourir tout votre code pour vérifier toutes les conditions.
  • Avec .NET 3.5, vous pouvez ajouter des méthodes d'extension aux énumérations pour les faire se comporter un peu plus comme des classes. Cependant, ajouter de vraies fonctionnalités de cette façon n'est pas si facile car ce n'est toujours pas une classe (vous finirez par utiliser switch- es dans vos méthodes d'extension sinon ailleurs.

Donc, pour une entité de type énumération avec un peu plus de fonctionnalités, vous devez prendre un certain temps et la créer en tant que classe, avec plusieurs choses à l'esprit:

  • Pour que votre classe se comporte comme une énumération, vous pouvez soit forcer chaque classe dérivée à instancier en tant que singleton, soit remplacer Equals pour permettre la comparaison de valeurs de différentes instances.
  • Si votre classe ressemble à une énumération, cela devrait signifier qu'elle ne devrait pas contenir d'état sérialisable - la désérialisation devrait être possible à partir de son seul type (une sorte d '"ID", comme vous l'avez dit).
  • La logique de persistance doit être limitée à la classe de base uniquement, sinon étendre votre "énumération" serait un cauchemar. Dans le cas où vous avez opté pour le modèle Singleton, vous devez garantir une désérialisation appropriée dans les instances singleton.
3
Groo

Chaque fois que vous vous trouvez en utilisant des "nombres magiques" dans le code, changez en énumérations. Outre le gain de temps (puisque la magie disparaîtra lorsque les bogues arriveront ...), cela économisera vos yeux et votre mémoire (des énumérations significatives rendent le code plus lisible et auto-documenté), car devinez quoi - vous êtes probablement la personne à entretenir et à développer votre propre code

3
Yordan Georgiev

Le stockage de la valeur de texte d'une énumération dans une base de données est moins préférable au stockage d'un entier, en raison de l'espace supplémentaire requis et de la recherche plus lente. Il est précieux en ce sens qu'il a plus de sens qu'un nombre, mais la base de données est destinée au stockage et la couche de présentation sert à rendre les choses agréables.

3
cjk

À mon humble avis, comme pour la partie code:

Vous devez toujours utiliser le type 'enum' pour vos énumérations, en gros, vous obtenez beaucoup de cadeaux si vous le faites: Tapez la sécurité, l'encapsulation et l'évitement des commutateurs, le support de certaines collections telles que EnumSet et EnumMap et clarté du code.

quant à la partie persistance, vous pouvez toujours conserver la représentation sous forme de chaîne de l'énumération et la recharger à l'aide de la méthode enum.valueOf (String).

2
MahdeTo

Je sais que c'est un ancien forum, que se passe-t-il si la base de données peut avoir d'autres choses qui s'y intègrent directement? Par exemple. lorsque la base de données résultante est le seul objectif du code. Ensuite, vous définirez les énumérations à chaque intégration. Mieux vaut alors les avoir dans la DB. Sinon, je suis d'accord avec le message d'origine.

1
Chalky