Acho que essa é outra pergunta sobre codificação embutida e práticas recomendadas. Digamos que eu tenha uma lista de valores, digamos frutas, armazenados no banco de dados (ele precisa estar no banco de dados, pois a tabela é usada para outros fins, como relatórios do SSRS), com um ID:
1 Apple
2 Banana
3 Grapes
Eu posso apresentá-los ao usuário, ele seleciona um, ele é armazenado em seu perfil como FavouriteFruit e o ID armazenado em seu registro no banco de dados.
Quando se trata de regras de negócios / lógica de domínio, quais são as recomendações para atribuir lógica a valores específicos. Digamos que se o usuário selecionou o Grapes, quero executar alguma tarefa extra, qual é a melhor maneira de referenciar o valor do Grapes:
// Hard coded name
if (user.FavouriteFruit.Name == "Grapes")
// Hard coded ID
if (user.FavoriteFruit.ID == 3) // Grapes
// Duplicate the list of fruits in an enum
if (user.FavouriteFruit.ID == (int)Fruits.Grapes)
ou alguma outra coisa?
Como é claro que o FavouriteFruit será usado em todo o aplicativo, a lista poderá ser adicionada ou editada.
Alguém pode decidir que deseja que 'Grapes' seja renomeado para 'Grape' e isso, obviamente, quebraria a opção de string codificada.
O código codificado não é totalmente claro, porém, como mostrado, você pode adicionar um comentário para identificar rapidamente qual item é.
A opção enum envolve a duplicação de dados do banco de dados, o que parece errado, pois pode ficar fora de sincronia.
De qualquer forma, agradeço antecipadamente por quaisquer comentários ou sugestões.
MyApplication.Grape.ID
é gago, por assim dizer. Uma "Apple" não é uma "Red_Apple", assim como o ID 3 também é 4. Portanto, o potencial de renomear "Apple" para "Red_Apple" não faz mais sentido do que declarar que 3 é 4 (e talvez até 3). O objetivo de um enum é abstrair seu DNA numérico. Talvez seja hora de realmente desacoplar chaves de banco de dados relacionais arbitrárias que literalmente não têm significado nos modelos de negócios de alguém.Respostas:
Evite seqüências de caracteres e constantes mágicas a todo custo. Eles estão completamente fora de questão, nem devem ser considerados opções. Isso parece deixar você com apenas uma opção viável: identificadores, ou seja, enumerações. No entanto, há também mais uma opção, que na minha opinião é a melhor. Vamos chamar esta opção "Objetos pré-carregados". Com objetos pré-carregados, você pode fazer o seguinte:
O que acabou de acontecer aqui é que eu obviamente carreguei toda a linha
Grape
na memória, por isso tenho seu ID pronto para uso em comparações. Se você estiver usando ORM (Object-Relational Mapping), ficará ainda melhor:(É por isso que eu chamo de "Objetos pré-carregados".)
Portanto, o que faço é que, durante a inicialização, carrego todas as minhas tabelas de "enumeração" (tabelas pequenas, como dias da semana, meses do ano, sexos etc.) na classe de domínio do aplicativo principal. Eu os carrego pelo nome, porque, obviamente,
MyApplication.Grape
deve receber a linha chamada "Grape", e afirmo que todos e cada um deles foram encontrados. Caso contrário, temos um erro de tempo de execução garantido durante a inicialização, que é o menos maligno de todos os erros de tempo de execução.fonte
Grape = fetchRow( Fruit.class, NameColumn, "Grape" );
e se você faça algo incorretamente,AssertionError
e você será informado.enum
teria sido uma sequência mágica. O ponto é a concentração de todas as ligações por nome em apenas um local , a validação de todas durante a inicialização e a segurança do tipo .A verificação na cadeia de caracteres é a mais legível, mas tem dupla função: é usada como identificador e como descrição (que pode mudar por razões não relacionadas).
Normalmente, divido as duas tarefas em campos separados:
Onde a descrição pode mudar (mas não "Grapes" para "Banana"), mas o código não pode mudar, nunca.
Embora isso ocorra principalmente porque nossos IDs são quase sempre gerados automaticamente e, portanto, não são adequados. Se você pode escolher IDs livremente, talvez possa garantir que eles estejam sempre corretos e usá-los.
Além disso, com que frequência alguém realmente edita "Grapes" para "Grape"? Talvez nada disso seja necessário.
fonte
O que você espera aqui é que a lógica de programação seja automaticamente adaptável à alteração de dados. Opções estáticas simples como o Enum não funcionam aqui porque você não pode adicionar enumerações extras no tempo de execução.
Alguns padrões que eu já vi:
Em geral, eu gosto que os dados sejam completos em termos de referência às ações que elas implicam - mesmo que as próprias ações possam ser implementadas em outro lugar. Qualquer código que determine ações independentes dos dados apenas fragmentou sua representação de dados, o que provavelmente irá divergir e levar a erros.
fonte
Armazená-los nos dois lugares (em uma tabela e em um ENUM) não é tão ruim assim. O raciocínio é o seguinte:
Armazenando-os em uma tabela de banco de dados, podemos impor integridade referencial no banco de dados por meio de chaves estrangeiras. Portanto, quando você associa uma pessoa ou qualquer entidade a uma fruta, é apenas uma fruta que existe na tabela do banco de dados.
Armazená-los como um ENUM também faz sentido, porque podemos escrever código sem seqüências de caracteres mágicas e isso torna o código mais legível. Sim, eles precisam se manter sincronizados, mas realmente seria difícil adicionar uma linha ao ENUM e uma nova instrução de inserção ao banco de dados.
Uma coisa, uma vez que um ENUM é definido, não altere seu valor. Por exemplo, se você tivesse:
NÃO renomeie Grape para Grapes. Basta adicionar um novo ENUM.
Se você precisar migrar dados, aplique uma atualização para mover toda a Grape para Grapes.
fonte
Você está certo em fazer esta pergunta; na verdade, é uma boa pergunta, enquanto você tenta se defender contra a avaliação de condições imprecisas.
Dito isto, a avaliação (suas
if
condições) não precisa necessariamente ser o foco de como você a contorna. Em vez disso, preste atenção à maneira como você propaga as alterações que causariam um problema 'fora de sincronia'.Abordagem de String
Se você deve usar strings, por que não expor a funcionalidade de alterar a lista por meio da interface do usuário? Projete o sistema para que, ao mudar
Grape
paraGrapes
, por exemplo, você atualize todos os registros atualmente em referênciaGrape
.Abordagem de identificação
Eu sempre prefiro fazer referência a um ID, apesar do comprometimento de alguma legibilidade.
The list may be added to
novamente pode ser algo de que você seria notificado se expusesse esse recurso de interface do usuário. Se você estiver preocupado com a reordenação de itens alterando o ID, propague essa alteração para todos os registros dependentes novamente. Da mesma forma que acima. Outra opção (seguindo a convenção de normalização adequada, seria ter uma coluna enum / id - e fazer referência a umaFruitDetail
tabela mais detalhada , que possui uma coluna 'Order' que você pode procurar).De qualquer forma, você pode ver que estou sugerindo controlar a alteração ou atualização da sua lista. Se você faz isso através do uso de um ORM ou de algum outro acesso a dados, é determinado pelas especificidades de sua tecnologia. O que você está fazendo, essencialmente, é exigir que as pessoas que se afastam do DB para essas mudanças - o que eu acho que está bem. A maioria dos principais CRMs fará o mesmo requisito.
fonte
Um problema muito comum. Embora a duplicação do lado do cliente de dados pareça violar os princípios DRY , isso se deve realmente à diferença de paradigma entre as camadas.
Ter a enumeração (ou qualquer outra coisa) fora de sintonia com o banco de dados também não é tão incomum. Você pode ter enviado outro valor para uma tabela de metadados para oferecer suporte a um novo recurso de relatórios que ainda não foi usado no código do lado do cliente.
Às vezes acontece o contrário também. Um novo valor de enumeração é adicionado ao lado do cliente, mas a atualização do banco de dados não pode ocorrer até que o DBA possa aplicar as alterações.
fonte
Supondo que estamos falando sobre o que é essencialmente uma pesquisa estática, a terceira opção - o enum - é basicamente a única opção sensata. É o que você faria se o banco de dados não estivesse envolvido, por isso faz sentido.
A pergunta então é sobre como manter enumerações e tabelas estáticas / de pesquisa no banco de dados sincronizadas e, infelizmente, isso não é um problema para o qual ainda tenho uma resposta completa.
Por opção, faço toda a manutenção do esquema no código e, portanto, posso manter um relacionamento entre a compilação do aplicativo e a versão esperada do esquema, por isso é fácil manter a pesquisa e a enumeração sincronizadas, mas é algo que é preciso lembrar. Faz. Seria melhor se fosse mais automatizado (e também um teste de integração automatizado para garantir que as enumerações e as pesquisas correspondessem), mas isso não é algo que eu já implementei.
fonte