Dilema
Eu tenho lido muitos livros de práticas recomendadas sobre práticas orientadas a objetos, e quase todos os livros que li tiveram uma parte em que eles dizem que as enums são um cheiro de código. Eu acho que eles perderam a parte em que explicam quando as enums são válidas.
Como tal, estou procurando diretrizes e / ou casos de uso em que as enumerações NÃO são um cheiro de código e, de fato, são uma construção válida.
Fontes:
"AVISO Como regra geral, as enums são odores de código e devem ser refatoradas para classes polimórficas. [8]" Seemann, Mark, Injection Dependency in .Net, 2011, p. 342
[8] Martin Fowler et al., Refatoração: Melhorando o Design do Código Existente (Nova York: Addison-Wesley, 1999), 82.
Contexto
A causa do meu dilema é uma API de negociação. Eles me fornecem um fluxo de dados do Tick enviando este método:
void TickPrice(TickType tickType, double value)
Onde enum TickType { BuyPrice, BuyQuantity, LastPrice, LastQuantity, ... }
Eu tentei criar um invólucro em torno dessa API porque quebrar alterações é o modo de vida dessa API. Eu queria acompanhar o valor de cada último tipo de tick recebido no meu wrapper e fiz isso usando um Dictionary of ticktypes:
Dictionary<TickType,double> LastValues
Para mim, isso parecia um uso adequado de um enum se eles fossem usados como chaves. Mas estou pensando duas vezes porque tenho um lugar onde tomo uma decisão com base nesta coleção e não consigo pensar em como eliminar a declaração do interruptor, poderia usar uma fábrica, mas essa fábrica ainda terá um mude a declaração em algum lugar. Pareceu-me que estou apenas mudando as coisas, mas ainda cheira.
É fácil encontrar o NÃO das enumerações, mas as DOs, não tão fáceis, e eu apreciaria se as pessoas pudessem compartilhar seus conhecimentos, os prós e os contras.
Segundas intenções
Algumas decisões e ações são baseadas nelas TickType
e não consigo pensar em uma maneira de eliminar enumerações / alternar declarações. A solução mais limpa que consigo pensar é usar uma fábrica e retornar uma implementação com base TickType
. Mesmo assim, ainda terei uma instrução switch que retorna a implementação de uma interface.
Listada abaixo está uma das classes de exemplo em que estou com dúvidas de que possa estar usando uma enumeração errada:
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;
}
}
}
fonte
enums as switch statements might be a code smell ...
Respostas:
As enumerações destinam-se a casos de uso quando você literalmente enumerou todos os valores possíveis que uma variável poderia receber. Sempre. Pense em casos de uso como dias da semana ou meses do ano ou valores de configuração de um registro de hardware. Coisas que são altamente estáveis e representáveis por um valor simples.
Lembre-se de que, se você estiver criando uma camada anticorrupção, não poderá evitar ter uma declaração de opção em algum lugar , devido ao design que está envolto, mas se fizer isso corretamente, poderá limitá-la a esse local e use polimorfismo em outro lugar.
fonte
Em primeiro lugar, um cheiro de código não significa que algo está errado. Isso significa que algo pode estar errado.
enum
cheira porque é frequentemente abusado, mas isso não significa que você precise evitá-los. Apenas você se encontra digitandoenum
, pare e procure por melhores soluções.O caso específico que ocorre com mais freqüência é quando as diferentes enumerações correspondem a tipos diferentes com comportamentos diferentes, mas com a mesma interface. Por exemplo, conversando com back-end diferentes, renderizando páginas diferentes etc. Isso é muito mais implementado naturalmente usando classes polimórficas.
No seu caso, o TickType não corresponde a diferentes comportamentos. Eles são tipos diferentes de eventos ou propriedades diferentes do estado atual. Então eu acho que este é o lugar ideal para um enum.
fonte
Ao transmitir enums de dados, não há cheiro de código
IMHO, ao transmitir dados usando
enums
para indicar que um campo pode ter um valor de um conjunto de valores restrito (que raramente muda) é bom.Considero preferível transmitir arbitrariamente
strings
ouints
. Seqüências de caracteres podem causar problemas por variações de ortografia e letras maiúsculas. Ints permitir a transmissão de valores fora da faixa e têm pouca semântica (por exemplo, eu recebo3
de seu serviço de negociação, o que significa isso?LastPrice
?LastQuantity
? Algo mais?Transmitir objetos e usar hierarquias de classes nem sempre é possível; por exemplo, o wcf não permite que o destinatário receba a distinção de qual classe foi enviada.
Uma outra razão pela qual não se deseja transmitir objetos de classe é que o serviço pode exigir um comportamento completamente diferente para um objeto transmitido (como
LastPrice
) que o cliente. Nesse caso, enviar a classe e seus métodos é indesejável.As instruções do switch são ruins?
IMHO, uma única
switch
declaração que chama diferentes construtores dependendo de umenum
não é um cheiro de código. Não é necessariamente melhor ou pior que outros métodos, como a base de reflexão em um nome de tipo; isso depende da situação real.Ter interruptores em
enum
todo o lugar é um cheiro de código, oop fornece alternativas que geralmente são melhores:fonte
int
. Meu wrapper é aquele que usa oTickType
que é convertido do recebidoint
. Vários eventos usam esse tipo de escala com assinaturas variadas e selvagens que são respostas a várias solicitações. É prática comum ter um usoint
contínuo para diferentes funções? por exemplo,TickPrice(int type, double value)
usa 1,3 e 6 paratype
enquantoTickSize(int type, double value
usa 2,4 e 5? Faz mesmo sentido separá-los em dois eventos?e se você fosse com um tipo mais complexo:
então você pode carregar seus tipos de reflexão ou construí-lo você mesmo, mas a principal coisa a ser feita aqui é que você está mantendo aberto para abrir o princípio próximo do SOLID
fonte
TL; DR
Para responder à pergunta, agora estou tendo dificuldade em pensar que as enums de tempo não são um cheiro de código em algum nível. Há uma certa intenção que eles declaram eficientemente (há um número limitado e limitado de possibilidades para esse valor), mas sua natureza inerentemente fechada os torna arquitetonicamente inferiores.
Com licença, refatorando toneladas do meu código legado. / suspiro; ^ D
Mas por que?
Muito boa revisão por que esse é o caso da LosTechies aqui :
Outro caso de implementação ...
A linha inferior é que, se você tem um comportamento que depende dos valores da enumeração, por que não ter implementações diferentes de uma interface semelhante ou classe pai que garante que esse valor exista? No meu caso, estou vendo diferentes mensagens de erro com base nos códigos de status REST. Ao invés de...
... talvez eu deva agrupar códigos de status nas aulas.
Cada vez que preciso de um novo tipo de IAmStatusResult, eu o codifico ...
... e agora posso garantir que o código anterior perceba que ele tem um
IAmStatusResult
escopo e faça referência ao seu, ementity.ErrorKey
vez do beco mais complicado_getErrorCode(403)
.E, mais importante, toda vez que adiciono um novo tipo de valor de retorno, nenhum outro código precisa ser adicionado para lidar com isso . O que sabíamos,
enum
eswitch
provavelmente eram cheiros de código.Lucro.
fonte
StatusResult
valor? Você poderia argumentar que o enum é útil como um atalho memorável para humanos nesse caso de uso, mas provavelmente eu ainda chamaria isso de cheiro de código, pois existem boas alternativas que não exigem a coleção fechada.enum
, você não tem que código de atualização cada vez que você adicionar ou remover um valor, e potencialmente muitos lugares, dependendo de como você estruturou seu código. Usar polimorfismo em vez de um enum é como garantir que seu código esteja no formato normal Boyce-Codd .Se o uso de uma enumeração é um cheiro de código ou não depende do contexto. Acho que você pode ter algumas idéias para responder sua pergunta se considerar o problema da expressão . Portanto, você tem uma coleção de diferentes tipos e uma coleção de operações e precisa organizar seu código. Existem duas opções simples:
Qual solução é melhor?
Se, como Karl Bielefeldt apontou, seus tipos são fixos e você espera que o sistema cresça principalmente adicionando novas operações nesses tipos, usar uma enumeração e ter uma instrução switch é uma solução melhor: sempre que você precisar de uma nova operação, basta implementar um novo procedimento, ao usar classes, você precisará adicionar um método a cada classe.
Por outro lado, se você espera ter um conjunto de operações bastante estável, mas acha que precisará adicionar mais tipos de dados ao longo do tempo, usar uma solução orientada a objetos é mais conveniente: à medida que novos tipos de dados devem ser implementados, você apenas continue adicionando novas classes implementando a mesma interface, enquanto que se você estivesse usando uma enum, teria que atualizar todas as instruções de opção em todos os procedimentos usando a enum.
Se você não conseguir classificar seu problema em uma das duas opções acima, poderá procurar soluções mais sofisticadas (consulte, por exemplo, novamente a página da Wikipedia) citada acima para uma breve discussão e algumas referências para leitura adicional).
Portanto, você deve tentar entender em que direção seu aplicativo pode evoluir e escolher uma solução adequada.
Como os livros que você se refere lidam com o paradigma orientado a objetos, não é de surpreender que sejam influenciados pelo uso de enums. No entanto, uma solução de orientação a objetos nem sempre é a melhor opção.
Bottomline: enums não são necessariamente um cheiro de código.
fonte
paint()
,show()
,close()
resize()
operações e widgets personalizados), em seguida, a abordagem orientada a objetos é melhor no sentido de que permite adicionar um novo tipo sem afetar demais muito código existente (você basicamente implementa uma nova classe, que é uma alteração local).