Dilemme
J'ai lu beaucoup de livres de bonnes pratiques sur les pratiques orientées objet, et presque tous les livres que j'ai lus avaient une partie où ils disent que les énumérations sont une odeur de code. Je pense qu'ils ont raté la partie où ils expliquent quand les énumérations sont valides.
En tant que tel, je recherche des directives et / ou des cas d'utilisation où les énumérations ne sont PAS une odeur de code et en fait une construction valide.
Sources:
"AVERTISSEMENT En règle générale, les énumérations sont des odeurs de code et doivent être refactorisées en classes polymorphes. [8]" Seemann, Mark, Dependency Injection in .Net, 2011, p. 342
[8] Martin Fowler et al., Refactoring: Improving the Design of Existing Code (New York: Addison-Wesley, 1999), 82.
Le contexte
La cause de mon dilemme est une API de trading. Ils me donnent un flux de données Tick en envoyant via cette méthode:
void TickPrice(TickType tickType, double value)
où enum TickType { BuyPrice, BuyQuantity, LastPrice, LastQuantity, ... }
J'ai essayé de créer un wrapper autour de cette API, car la rupture des changements est le mode de vie de cette API. Je voulais garder une trace de la valeur de chaque dernier type de tick reçu sur mon wrapper et je l'ai fait en utilisant un Dictionary of ticktypes:
Dictionary<TickType,double> LastValues
Pour moi, cela semblait être une bonne utilisation d'une énumération si elles étaient utilisées comme clés. Mais j'ai des doutes parce que j'ai un endroit où je prends une décision basée sur cette collection et je ne peux pas penser à un moyen d'éliminer la déclaration de changement, je pourrais utiliser une usine mais cette usine aura toujours un basculer la déclaration quelque part. Il me semblait que je ne fais que bouger mais ça sent encore.
Il est facile de trouver les NE PAS des énumérations, mais les DO, pas si faciles, et j'apprécierais que les gens puissent partager leur expertise, les avantages et les inconvénients.
Doutes
Certaines décisions et actions sont basées sur celles TickType
- ci et je n'arrive pas à penser à un moyen d'éliminer les instructions enum / switch. La solution la plus propre à laquelle je peux penser est d'utiliser une usine et de renvoyer une implémentation basée sur TickType
. Même alors, j'aurai toujours une instruction switch qui renvoie une implémentation d'une interface.
La liste ci-dessous est l'un des exemples de classes où j'ai des doutes quant à l'utilisation incorrecte d'une énumération:
public class ExecutionSimulator
{
Dictionary<TickType, double> LastReceived;
void ProcessTick(TickType tickType, double value)
{
//Store Last Received TickType value
LastReceived[tickType] = value;
//Perform Order matching only on specific TickTypes
switch(tickType)
{
case BidPrice:
case BidSize:
MatchSellOrders();
break;
case AskPrice:
case AskSize:
MatchBuyOrders();
break;
}
}
}
enums as switch statements might be a code smell ...
Réponses:
Les énumérations sont destinées aux cas d'utilisation lorsque vous avez littéralement énuméré toutes les valeurs possibles qu'une variable pourrait prendre. Déjà. Pensez à des cas d'utilisation comme les jours de la semaine ou les mois de l'année ou les valeurs de configuration d'un registre matériel. Des choses à la fois très stables et représentables par une simple valeur.
Gardez à l'esprit que si vous créez une couche anti-corruption, vous ne pouvez pas éviter d'avoir une instruction switch quelque part , en raison de la conception que vous enveloppez, mais si vous le faites correctement, vous pouvez la limiter à cet endroit et utiliser le polymorphisme ailleurs.
la source
Premièrement, une odeur de code ne signifie pas que quelque chose ne va pas. Cela signifie que quelque chose ne va pas.
enum
sent parce qu'il est souvent maltraité, mais cela ne signifie pas que vous devez les éviter. Il vous suffit de taperenum
, d'arrêter et de rechercher de meilleures solutions.Le cas particulier qui se produit le plus souvent est lorsque les différentes énumérations correspondent à différents types avec des comportements différents mais la même interface. Par exemple, parler à différents backends, rendre différentes pages, etc. Celles-ci sont beaucoup plus naturellement implémentées à l'aide de classes polymorphes.
Dans votre cas, le TickType ne correspond pas à des comportements différents. Ce sont différents types d'événements ou différentes propriétés de l'état actuel. Je pense donc que c'est l'endroit idéal pour une énumération.
la source
Lors de la transmission des énumérations de données, aucune odeur de code
À mon humble avis, lors de la transmission de données en utilisant
enums
pour indiquer qu'un champ peut avoir une valeur à partir d'un ensemble restreint (changeant rarement) de valeurs est bon.Je considère préférable de transmettre arbitrairement
strings
ouints
. Les chaînes peuvent provoquer des problèmes par des variations d'orthographe et de capitalisation. Ints permettre la transmission de valeurs de la plage et ont peu sémantique (par exemple , je reçois3
de votre service commercial, qu'est-ce que cela signifie?LastPrice
?LastQuantity
? Autre chose?La transmission d'objets et l'utilisation de hiérarchies de classes n'est pas toujours possible; par exemple, wcf ne permet pas à l'extrémité réceptrice de distinguer la classe qui a été envoyée.
Une autre raison pour laquelle on ne voudrait pas transmettre des objets de classe est que le service peut nécessiter un comportement complètement différent pour un objet transmis (tel que
LastPrice
) que le client. Dans ce cas, l'envoi de la classe et de ses méthodes n'est pas souhaité.Les instructions de commutation sont-elles mauvaises?
À mon humble avis , une déclaration unique
switch
qui appelle différents constructeurs en fonction d'unenum
n'est pas une odeur de code. Elle n'est pas nécessairement meilleure ou pire que d'autres méthodes, telles que la réflexion basée sur un nom de type; cela dépend de la situation réelle.Avoir des interrupteurs
enum
partout est une odeur de code, oop fournit des alternatives souvent meilleures:la source
int
. Mon wrapper est celui qui utiliseTickType
qui est casté à partir du reçuint
. Plusieurs événements utilisent ce type de tick avec des signatures variant de manière sauvage qui sont des réponses à diverses demandes. Est-ce une pratique courante d'avoir une utilisationint
continue pour différentes fonctions? par exemple,TickPrice(int type, double value)
utilise 1,3 et 6 pourtype
tandis queTickSize(int type, double value
utilise 2,4 et 5? Est-il même logique de les séparer en deux événements?Et si vous optiez pour un type plus complexe:
alors vous pouvez charger vos types à partir de la réflexion ou le construire vous-même, mais la principale chose à faire ici est que vous maintenez le principe Open Close de SOLID
la source
TL; DR
Pour répondre à la question, j'ai maintenant du mal à penser à un moment où les énumérations ne sont pas une odeur de code à un certain niveau. Il y a une certaine intention qu'ils déclarent efficacement (il y a un nombre clairement limité et limité de possibilités pour cette valeur), mais leur nature intrinsèquement fermée les rend architecturalement inférieurs.
Excusez-moi alors que je refaçonne des tonnes de mon ancien code. / soupir; ^ D
Mais pourquoi?
Très bonne critique pourquoi c'est le cas de LosTechies ici :
Un autre cas d'implémentation ...
En fin de compte, si vous avez un comportement qui dépend des valeurs d'énumération, pourquoi ne pas avoir à la place différentes implémentations d'une interface ou d'une classe parent similaire qui garantit que cette valeur existe? Dans mon cas, je regarde différents messages d'erreur basés sur des codes d'état REST. Au lieu de...
... je devrais peut-être envelopper les codes de statut dans les classes.
Ensuite, chaque fois que j'ai besoin d'un nouveau type d'IAmStatusResult, je le code ...
... et maintenant je peux m'assurer que le code antérieur se rend compte qu'il a une
IAmStatusResult
portée et référence sonentity.ErrorKey
au lieu du cul-de-sac le plus compliqué_getErrorCode(403)
.Et, plus important encore, chaque fois que j'ajoute un nouveau type de valeur de retour, aucun autre code n'a besoin d'être ajouté pour le gérer . Whaddya sait,
enum
etswitch
étaient probablement des odeurs de code.Profit.
la source
StatusResult
valeur? Vous pourriez faire valoir que l'énumération est utile en tant que raccourci mémorisable dans ce cas d'utilisation, mais j'appellerais probablement encore cela une odeur de code, car il existe de bonnes alternatives qui ne nécessitent pas la collection fermée.enum
, vous ne devez le code de mise à jour chaque fois que vous ajoutez ou supprimez une valeur, et potentiellement beaucoup d'endroits, en fonction de la façon dont vous structuré votre code. Utiliser le polymorphisme au lieu d'une énumération revient en quelque sorte à s'assurer que votre code est au format normal Boyce-Codd .Que l'utilisation d'une énumération soit ou non une odeur de code dépend du contexte. Je pense que vous pouvez obtenir quelques idées pour répondre à votre question si vous considérez le problème d'expression . Donc, vous avez une collection de différents types et une collection d'opérations sur eux, et vous devez organiser votre code. Il existe deux options simples:
Quelle solution est la meilleure?
Si, comme l'a souligné Karl Bielefeldt, vos types sont fixes et que vous vous attendez à ce que le système se développe principalement en ajoutant de nouvelles opérations sur ces types, alors utiliser une énumération et avoir une instruction switch est une meilleure solution: chaque fois que vous avez besoin d'une nouvelle opération, vous implémentez simplement une nouvelle procédure alors qu'en utilisant des classes, vous devrez ajouter une méthode à chaque classe.
D'un autre côté, si vous vous attendez à avoir un ensemble d'opérations plutôt stable mais que vous pensez que vous devrez ajouter plus de types de données au fil du temps, l'utilisation d'une solution orientée objet est plus pratique: comme de nouveaux types de données doivent être implémentés, vous venez continuez à ajouter de nouvelles classes implémentant la même interface, alors que si vous utilisez une énumération, vous devrez mettre à jour toutes les instructions switch dans toutes les procédures utilisant l'énumération.
Si vous ne pouvez pas classer votre problème dans l'une des deux options ci-dessus, vous pouvez rechercher des solutions plus sophistiquées (voir par exemple à nouveau la page Wikipedia citée ci-dessus pour une brève discussion et quelques références pour une lecture plus approfondie).
Vous devez donc essayer de comprendre dans quelle direction votre application peut évoluer, puis choisir une solution appropriée.
Étant donné que les livres auxquels vous vous référez traitent du paradigme orienté objet, il n'est pas surprenant qu'ils soient biaisés contre l'utilisation d'énumérations. Cependant, une solution d'orientation d'objet n'est pas toujours la meilleure option.
Conclusion: les énumérations ne sont pas nécessairement une odeur de code.
la source
paint()
,show()
,close()
resize()
approche des opérations et des widgets personnalisés) , puis l'orienté objet est mieux dans le sens où il permet d'ajouter un nouveau type sans affecter trop beaucoup de code existant (vous implémentez essentiellement une nouvelle classe, qui est un changement local).