Dilema
He estado leyendo muchos libros de mejores prácticas sobre prácticas orientadas a objetos, y casi todos los libros que he leído tienen una parte donde dicen que las enumeraciones son un olor a código. Creo que se han perdido la parte en la que explican cuándo las enumeraciones son válidas.
Como tal, estoy buscando pautas y / o casos de uso donde las enumeraciones NO son un olor a código y, de hecho, una construcción válida.
Fuentes:
"ADVERTENCIA Como regla general, las enumeraciones son olores de código y deben refactorizarse a clases polimórficas. [8]" Seemann, Mark, Dependency Injection en .Net, 2011, p. 342
[8] Martin Fowler et al., Refactoring: Improving the Design of Existing Code (Nueva York: Addison-Wesley, 1999), 82.
Contexto
La causa de mi dilema es una API comercial. Me dan una secuencia de datos de Tick enviándome a través de este método:
void TickPrice(TickType tickType, double value)
dónde enum TickType { BuyPrice, BuyQuantity, LastPrice, LastQuantity, ... }
Intenté hacer un envoltorio alrededor de esta API porque romper los cambios es la forma de vida de esta API. Quería hacer un seguimiento del valor de cada último tipo de tick recibido en mi contenedor y lo hice usando un Diccionario de tipos de tick:
Dictionary<TickType,double> LastValues
Para mí, esto parecía un uso adecuado de una enumeración si se usan como claves. Pero estoy teniendo dudas porque tengo un lugar donde tomo una decisión basada en esta colección y no puedo pensar en cómo podría eliminar la declaración de cambio, podría usar una fábrica, pero esa fábrica aún tendrá un cambiar la declaración en alguna parte. Me pareció que solo estaba moviendo cosas, pero todavía huele.
Es fácil encontrar los DON'T de las enumeraciones, pero los DO no son tan fáciles, y agradecería que las personas puedan compartir su experiencia, los pros y los contras.
Segundos pensamientos
Algunas decisiones y acciones se basan en estos TickType
y parece que no puedo pensar en una forma de eliminar las declaraciones enum / switch. La solución más limpia que se me ocurre es usar una fábrica y devolver una implementación basada en TickType
. Aún así, tendré una declaración de cambio que devuelve una implementación de una interfaz.
A continuación se enumera una de las clases de muestra donde tengo dudas de que podría estar usando una enumeración incorrecta:
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 ...
Respuestas:
Las enumeraciones están destinadas a casos de uso cuando literalmente ha enumerado todos los valores posibles que una variable podría tomar. Nunca. Piense en casos de uso como días de la semana o meses del año o valores de configuración de un registro de hardware. Cosas que son altamente estables y representables por un valor simple.
Tenga en cuenta que si está creando una capa anticorrupción, no puede evitar tener una declaración de cambio en alguna parte , debido al diseño que está envolviendo, pero si lo hace bien, puede limitarlo a ese lugar y usar polimorfismo en otro lugar.
fuente
En primer lugar, un olor a código no significa que algo esté mal. Significa que algo podría estar mal.
enum
huele porque se abusa con frecuencia, pero eso no significa que tengas que evitarlos. Simplemente te encuentras escribiendoenum
, detente y busca mejores soluciones.El caso particular que ocurre con mayor frecuencia es cuando las diferentes enumeraciones corresponden a diferentes tipos con diferentes comportamientos pero la misma interfaz. Por ejemplo, hablar con diferentes backends, renderizar diferentes páginas, etc. Estos se implementan mucho más naturalmente usando clases polimórficas.
En su caso, el TickType no corresponde a comportamientos diferentes. Son diferentes tipos de eventos o diferentes propiedades del estado actual. Así que creo que este es el lugar ideal para una enumeración.
fuente
Cuando se transmiten datos, las enumeraciones no huelen a código
En mi humilde opinión, cuando se transmiten datos utilizando
enums
para indicar que un campo puede tener un valor de un conjunto restringido (rara vez cambia) de valores es bueno.Considero preferible transmitir arbitrariamente
strings
oints
. Las cadenas pueden causar problemas por variaciones en la ortografía y las mayúsculas. Permite transmitir valores fuera de rango y tener poca semántica (por ejemplo, recibo3
de su servicio comercial, ¿qué significa?LastPrice
¿LastQuantity
? ¿Algo más?Transmitir objetos y usar jerarquías de clases no siempre es posible; por ejemplo, wcf no permite que el extremo receptor distinga qué clase se ha enviado.
Otra razón por la que uno no querría transmitir objetos de clase es que el servicio puede requerir un comportamiento completamente diferente para un objeto transmitido (como
LastPrice
) que el cliente. En ese caso, el envío de la clase y sus métodos no es deseado.¿Son malas las declaraciones de cambio?
En mi humilde opinión, una declaración única
switch
que llama a diferentes constructores dependiendo de unenum
no es un código de olor. No es necesariamente mejor o peor que otros métodos, como la base de reflexión en un nombre de tipo; Esto depende de la situación real.Tener interruptores en
enum
todo el lugar es un olor a código, oop ofrece alternativas que a menudo son mejores:fuente
int
. Mi contenedor es el que usa elTickType
que se emite desde el recibidoint
. Varios eventos usan este ticktype con firmas variables que responden a varias solicitudes. ¿Es una práctica común tener un usoint
continuo para diferentes funciones? por ejemplo,TickPrice(int type, double value)
utiliza 1,3 y 6 paratype
mientrasTickSize(int type, double value
usa 2,4 y 5? ¿Tiene sentido separarlos en dos eventos?¿Qué pasa si fue con un tipo más complejo:
entonces podría cargar sus tipos desde la reflexión o construirlo usted mismo, pero lo más importante aquí es que está manteniendo abierto el principio de SOLID
fuente
TL; DR
Para responder a la pregunta, ahora estoy teniendo dificultades para pensar en un momento en que las enumeraciones no son un olor a código en algún nivel. Hay una cierta intención que declaran eficientemente (hay un número limitado de posibilidades claramente limitadas para este valor), pero su naturaleza inherentemente cerrada los hace arquitectónicamente inferiores.
Disculpe mientras refactorizo toneladas de mi código heredado. / suspiro; ^ D
¿Pero por qué?
Muy buena revisión por qué ese es el caso de LosTechies aquí :
Otro caso de implementación ...
La conclusión es que si tiene un comportamiento que depende de los valores de enumeración, ¿por qué no tener implementaciones diferentes de una interfaz similar o una clase principal que garantice la existencia de ese valor? En mi caso, estoy viendo diferentes mensajes de error basados en códigos de estado REST. En lugar de...
... quizás debería envolver los códigos de estado en las clases.
Luego, cada vez que necesito un nuevo tipo de IAmStatusResult, lo codifico ...
... y ahora puedo asegurarme de que el código anterior se da cuenta de que tiene un
IAmStatusResult
alcance y hacer referencia a su enentity.ErrorKey
lugar del más complicado, deadend_getErrorCode(403)
.Y, lo que es más importante, cada vez que agrego un nuevo tipo de valor de retorno, no es necesario agregar ningún otro código para manejarlo . Whaddya saber, el
enum
yswitch
era probable código de olores.Lucro.
fuente
StatusResult
valor? Se podría argumentar que la enumeración es útil como un atajo memorable para los humanos en ese caso de uso, pero probablemente todavía lo llamaría un olor a código, ya que hay buenas alternativas que no requieren la colección cerrada.enum
, usted no tiene que actualizar el código cada vez que se añade o elimina un valor, y potencialmente una gran cantidad de lugares, dependiendo de cómo se estructurado su código. Usar polimorfismo en lugar de una enumeración es como asegurarse de que su código esté en forma normal de Boyce-Codd .Si usar una enumeración es un olor a código o no, depende del contexto. Creo que puede obtener algunas ideas para responder a su pregunta si considera el problema de expresión . Entonces, tiene una colección de diferentes tipos y una colección de operaciones en ellos, y necesita organizar su código. Hay dos opciones simples:
¿Qué solución es mejor?
Si, como señaló Karl Bielefeldt, sus tipos son fijos y espera que el sistema crezca principalmente al agregar nuevas operaciones en estos tipos, entonces usar una enumeración y tener una declaración de cambio es una mejor solución: cada vez que necesite una nueva operación, simplemente implemente un nuevo procedimiento, mientras que al usar clases tendría que agregar un método a cada clase.
Por otro lado, si espera tener un conjunto de operaciones bastante estable pero cree que tendrá que agregar más tipos de datos a lo largo del tiempo, usar una solución orientada a objetos es más conveniente: como se deben implementar nuevos tipos de datos, simplemente continúe agregando nuevas clases implementando la misma interfaz, mientras que si estuviera usando una enumeración, tendría que actualizar todas las instrucciones de cambio en todos los procedimientos que utilizan la enumeración.
Si no puede clasificar su problema en cualquiera de las dos opciones anteriores, puede buscar soluciones más sofisticadas (consulte, por ejemplo, nuevamente la página de Wikipedia citada anteriormente para una breve discusión y alguna referencia para leer más).
Por lo tanto, debe intentar comprender en qué dirección puede evolucionar su aplicación y luego elegir una solución adecuada.
Dado que los libros a los que se refiere tratan el paradigma orientado a objetos, no es sorprendente que estén predispuestos a usar enumeraciones. Sin embargo, una solución de orientación a objetos no siempre es la mejor opción.
En pocas palabras: las enumeraciones no son necesariamente un olor a código.
fuente
paint()
,show()
,close()
resize()
operaciones y widgets personalizados), entonces el enfoque orientado a objetos es mejor en el sentido de que permite añadir un nuevo tipo sin afectar demasiado mucho código existente (básicamente implementa una nueva clase, que es un cambio local).