Quels sont les inconvénients du mappage des identifiants intégraux aux énumérations?

16

J'ai pensé à créer des types personnalisés pour des identifiants comme celui-ci:

public enum CustomerId : int { /* intentionally empty */ }
public enum OrderId : int { }
public enum ProductId : int { }

Ma principale motivation pour cela est d'empêcher le type de bogue où vous passez accidentellement un orderItemId à une fonction qui attendait un orderItemDetailId.

Il semble que les énumérations fonctionnent parfaitement avec tout ce que je voudrais utiliser dans une application Web .NET typique:

  • Le routage MVC fonctionne bien
  • La sérialisation JSON fonctionne bien
  • Chaque ORM auquel je peux penser fonctionne bien

Alors maintenant je me demande, "pourquoi ne devrais-je pas faire ça?" Ce sont les seuls inconvénients auxquels je peux penser:

  • Cela peut confondre d'autres développeurs
  • Il introduit une incohérence dans votre système si vous avez des identifiants non intégraux.
  • Cela peut nécessiter un casting supplémentaire, comme (CustomerId)42. Mais je ne pense pas que ce sera un problème, car le routage ORM et MVC vous fournira généralement des valeurs du type enum directement.

Alors ma question est, qu'est-ce que je manque? C'est probablement une mauvaise idée, mais pourquoi?

default.kramer
la source
3
Ceci est appelé « microtyping », vous pouvez google autour (par exemple , c'est pour Java, les détails sont différents, mais la motivation même)
QbD
J'ai tapé deux réponses partielles, puis je les ai supprimées parce que j'ai réalisé que je pensais mal à cela. Je suis d'accord avec vous que "c'est probablement une mauvaise idée, mais pourquoi?"
user2023861

Réponses:

7

C'est une excellente idée de créer un type pour chaque type d'identifiant, principalement pour les raisons que vous avez indiquées. Je ne ferais pas cela en implémentant le type enum, car en faire une structure ou une classe appropriée vous donne plus d'options:

  • Vous pouvez ajouter une conversion implicite à int. Quand on y pense, c'est l'autre direction, int -> CustomerId, qui est la problématique qui mérite une confirmation explicite du consommateur.
  • Vous pouvez ajouter des règles métier qui spécifient quels identifiants sont acceptables et lesquels ne le sont pas. Il ne s'agit pas seulement d'une vérification faible si l'int est positif: parfois, la liste de tous les ID possibles est conservée dans la base de données, mais change uniquement pendant le déploiement. Ensuite, vous pouvez facilement vérifier que l'ID donné existe par rapport au résultat mis en cache de la requête appropriée. De cette façon, vous pouvez garantir que votre application échouera rapidement en présence de données non valides.
  • Les méthodes sur l'identifiant peuvent vous aider à effectuer des vérifications d'existence de bases de données coûteuses ou à obtenir l'objet entité / domaine correspondant.

Vous pouvez lire sur l'implémentation de cela dans l'article Functional C #: Obsession primitive .

Lukáš Lánský
la source
1
je dirais que vous ne voulez certainement pas envelopper chaque int dans une classe mais dans une structure
jk.
Bonne remarque, j'ai légèrement mis à jour la réponse.
Lukáš Lánský
3

C'est un hack intelligent, mais toujours un hack qui abuse d'une fonctionnalité pour quelque chose qui n'est pas le but recherché. Les hacks ont un coût de maintenabilité, il ne suffit donc pas de trouver d'autres inconvénients, il doit également avoir des avantages significatifs par rapport à la manière la plus idiomatique de résoudre le même problème.

La manière idiomatique serait de créer un type ou une structure de wrapper avec un seul champ. Cela vous donnera le même type de sécurité d'une manière plus explicite et idiomatique. Bien que votre proposition soit certes intelligente, je ne vois aucun avantage significatif à utiliser des types de wrapper.

JacquesB
la source
1
Le principal avantage d'une énumération est qu'elle "fonctionne" comme vous le souhaitez avec MVC, la sérialisation, les ORM, etc. J'ai créé des types personnalisés (structures et classes) dans le passé et vous finissez par écrire plus de code que vous. devrait le faire pour que ces frameworks / bibliothèques fonctionnent avec vos types personnalisés.
default.kramer
La sérialisation doit fonctionner de manière transparente dans les deux cas, mais par exemple. ORM, vous devez configurer une sorte de conversion explicite. Mais c'est le prix d'une langue fortement typée.
JacquesB
2

De mon POV, l'idée est bonne. L'intention de donner différents types à des classes d'identificateurs sans rapport, et d'exprimer autrement la sémantique via des types et de laisser le compilateur le vérifier, est bonne.

Je ne sais pas comment cela fonctionne en termes de performances .NET, par exemple les valeurs enum sont-elles nécessairement encadrées. Le code OTOH qui fonctionne avec une base de données est probablement lié aux E / S de toute façon et les identifiants encadrés ne seront pas un goulot d'étranglement.

Dans un monde parfait, les identifiants seraient immuables et ne seraient comparables que pour l'égalité; les octets réels seraient un détail d'implémentation. Les identifiants indépendants auraient des types incompatibles, vous ne pourriez donc pas les utiliser l'un par l'autre par erreur. Votre solution correspond pratiquement à la facture.

9000
la source
-1

Vous ne pouvez pas faire cela, les énumérations doivent être prédéfinies. Donc, à moins que vous ne vouliez prédéfinir tout le spectre des valeurs possibles des identifiants client, votre type sera inutile.

Si vous lancez, vous défieriez votre objectif principal d'empêcher d'assigner accidentellement un identifiant de type A à un identifiant de type B. Les énumérations ne sont donc pas utiles à votre objectif.

Cela soulève cependant la question de savoir s'il serait utile de créer vos types pour l'ID client, l'ID de commande et l'ID de produit en descendant d'Int32 (ou Guid). Je peux penser à des raisons pour lesquelles ce serait faux.

Le but d'un identifiant est l'identification. Les types intégraux sont déjà parfaits pour cela, ils ne s'identifient plus en descendant d'eux. Aucune spécialisation n'est requise ni souhaitée, votre monde ne sera pas mieux représenté en ayant différents types d'identifiants. Du point de vue OO, ce serait mauvais.

Avec votre suggestion, vous voulez plus que la sécurité du type, vous voulez la sécurité de la valeur et cela dépasse le domaine de la modélisation, c'est un problème opérationnel.

Martin Maat
la source
1
Non, les énumérations n'ont pas à être prédéfinies dans .NET. Et le fait est que la conversion ne se produit presque jamais, par exemple, public ActionResult GetCustomer(CustomerId custId)aucune conversion n'est nécessaire - le framework MVC fournira la valeur.
default.kramer
2
Je ne suis pas d'accord. Pour moi, cela est parfaitement logique du point de vue OO. Int32 n'a pas de sémantique, c'est un type purement "technique". Mais j'ai besoin d'un type abstrait d'identifiant qui sert à identifier des objets et qui se trouve être implémenté en tant qu'Int32 (mais qui pourrait être long ou autre, n'a pas vraiment d'importance). Nous avons des spécialisations concrètes comme ProductId qui descendent de l'identifiant qui identifie l'instance concrète de ProductId. Cela n'a aucun sens logique d'affecter la valeur de OrderItemId à ProductId (c'est clairement une erreur), donc c'est génial que le système de type puisse nous protéger de cela.
qbd
1
Je ne savais pas que C # était aussi flexible en ce qui concerne l'utilisation des énumérations. Cela me déroute certainement en tant que développeur habitué au fait (?) Que les énumérations sont, bien, des énumérations de valeurs possibles. Ils spécifient généralement une plage d'ID nommés. Alors maintenant, ce type est "abusé" dans le sens où il n'y a pas de plage et il n'y a pas de noms, juste le type est laissé. Et apparemment, le type n'est pas si sûr non plus. Autre chose: l'exemple est évidemment une application de base de données et à un moment donné, votre "énumération" sera mappée à un champ de type intégral à quel moment vous perdriez votre type présumé en toute sécurité après tout.
Martin Maat