Quais são as desvantagens de mapear identificadores integrais para enumerações?


16

Eu estive pensando em criar tipos personalizados para identificadores como este:

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

Minha principal motivação para isso é evitar o tipo de bug em que você acidentalmente passa um orderItemId para uma função que esperava um orderItemDetailId.

Parece que as enums funcionam perfeitamente com tudo o que eu gostaria de usar em um aplicativo Web .NET típico:

  • O roteamento MVC funciona bem
  • A serialização JSON funciona bem
  • Cada ORM em que consigo pensar funciona bem

Então agora estou me perguntando: "por que não devo fazer isso?" Estas são as únicas desvantagens em que consigo pensar:

  • Pode confundir outros desenvolvedores
  • Introduz inconsistência no seu sistema se você tiver algum identificador não integral.
  • Pode exigir elenco extra, como (CustomerId)42. Mas não acho que isso seja um problema, pois o roteamento ORM e MVC normalmente estará entregando valores do tipo enum diretamente.

Então, minha pergunta é: o que estou perdendo? Esta é provavelmente uma má ideia, mas por quê?



3
Isso é chamado de "microtipagem", você pode pesquisar no Google (por exemplo, isso é para Java, os detalhes são diferentes, mas a mesma motivação é)
qbd

Eu digitei duas respostas parciais e as excluí porque percebi que estava pensando errado. Concordo com você que "Esta é provavelmente uma má ideia, mas por quê?"
precisa saber é o seguinte

Respostas:


7

É uma ótima idéia criar um tipo para cada tipo de identificador, principalmente pelos motivos que você declarou. Eu não faria isso implementando o tipo como enum, porque torná-lo uma estrutura ou classe adequada oferece mais opções:

  • Você pode adicionar conversão implícita em int. Quando você pensa sobre isso, é a outra direção, int -> CustomerId, que é a problemática que merece confirmação explícita do consumidor.
  • Você pode adicionar regras de negócios que especificam quais identificadores são aceitáveis ​​e quais não são. Não se trata apenas de uma verificação fraca se o int é positivo: às vezes, a lista de todos os IDs possíveis é mantida no banco de dados, mas muda apenas durante a implantação. Em seguida, você pode facilmente verificar se o ID fornecido existe em relação ao resultado em cache da consulta adequada. Dessa forma, você pode garantir que seu aplicativo falhe rapidamente na presença de dados inválidos.
  • Os métodos no identificador podem ajudá-lo a fazer verificações caras da existência do banco de dados ou a obter o objeto de entidade / domínio correspondente.

Você pode ler sobre como implementar isso no artigo C # funcional: obsessão primitiva .


1
Eu diria que você quase certamente não quer agrupar cada int em uma classe, mas em um struct
jk.

Boa observação, eu atualizei a resposta um pouco.
Lukáš Lánský

3

É um truque inteligente, mas ainda assim um abuso que abusa de um recurso para algo que não é o objetivo pretendido. Os hackers têm um custo em manutenção, portanto, não é suficiente que você não encontre outras desvantagens, também precisa ter benefícios significativos em comparação com a maneira mais idiomática de resolver o mesmo problema.

A maneira idiomática seria criar um tipo de wrapper ou estrutura com um único campo. Isso fornecerá o mesmo tipo de segurança de maneira mais explícita e idiomática. Embora sua proposta seja reconhecidamente inteligente, não vejo vantagens significativas no uso de tipos de wrapper.


1
A principal vantagem de um enum é que "simplesmente funciona" da maneira que você deseja com MVC, serialização, ORMs, etc. Eu criei tipos personalizados (estruturas e classes) no passado e você acaba escrevendo mais código do que você para que essas estruturas / bibliotecas funcionem com seus tipos personalizados.
default.kramer

A serialização deve funcionar de maneira transparente de qualquer maneira, mas por exemplo. ORMs, você precisa configurar algum tipo de conversão explícita. Mas este é o preço de uma linguagem fortemente tipada.
JacquesB

2

Do meu ponto de vista, a ideia é boa. A intenção de fornecer tipos diferentes para classes de identificadores não relacionados e, de outro modo, expressar semântica por meio de tipos e deixar que o compilador verifique isso, é boa.

Não sei como ele funciona no .NET em termos de desempenho, por exemplo, os valores de enum estão necessariamente em caixas. O código OTOH que funciona com um banco de dados provavelmente está ligado à E / S e os identificadores em caixa não terão nenhum gargalo.

Em um mundo perfeito, os identificadores seriam imutáveis ​​e apenas comparáveis ​​em termos de igualdade; bytes reais seriam um detalhe de implementação. Identificadores não relacionados teriam tipos incompatíveis, portanto você não pode usar um por outro por engano. Sua solução praticamente se encaixa na conta.


-1

Você não pode fazer isso, as enumerações devem ser predefinidas. Portanto, a menos que você esteja disposto a pré-definir todo o espectro de possíveis valores de IDs de clientes, seu tipo será inútil.

Se você transmitir, desafiaria seu objetivo principal de impedir atribuir acidentalmente um ID do tipo A a um ID do tipo B. Portanto, as enums não são úteis para o seu propósito.

Isso levanta a questão, porém, se seria útil criar seus tipos para identificação do cliente, identificação do pedido e identificação do produto descendo do Int32 (ou Guid). Eu posso pensar em algumas razões pelas quais isso estaria errado.

O objetivo de um ID é identificação. Tipos integrais já são perfeitos para isso, eles não são mais identificados descendo deles. Não há especialização necessária nem desejada, seu mundo não será representado melhor por ter tipos diferentes para identificadores. Então, do ponto de vista de OO, seria ruim.

Com sua sugestão, você deseja mais do que segurança de tipo, deseja segurança de valor e isso está além do domínio de modelagem, que é um problema operacional.


1
Não, as enumerações não precisam ser predefinidas no .NET. E o ponto é que a transmissão quase nunca acontece, por exemplo, public ActionResult GetCustomer(CustomerId custId)nenhuma transmissão é necessária - a estrutura MVC fornecerá o valor.
default.kramer

2
Eu não concordo Para mim, faz todo o sentido da perspectiva do OO. Int32 não tem semântica, é puramente "técnico". Mas eu preciso de um tipo abstrato de identificador, que serve para identificar objetos e que é implementado como Int32 (mas pode ser longo ou o que for, não importa). Temos especializações concretas como ProductId, que descendem de Identifier, que identificam instâncias concretas de ProductId. Não faz sentido lógico atribuir o valor de OrderItemId a ProductId (é claramente um erro), por isso é ótimo que o sistema de tipos possa nos proteger disso.
Qd 02/02

1
Eu não sabia que o C # era flexível quanto ao uso de enums. Certamente é confuso para mim, como desenvolvedor, acostumado ao fato (?) De que enumerações são, bem, enumerações de valores possíveis. Eles geralmente especificam um intervalo de IDs nomeados. Portanto, agora esse tipo é "abusado" no sentido de que não há intervalo e não há nomes, apenas o tipo é deixado. E, aparentemente, o tipo também não é tão seguro. Outra coisa: o exemplo é obviamente um aplicativo de banco de dados e, em algum momento, seu "enum" será mapeado para um campo de tipo integral, no momento em que você perderia seu tipo presumido com segurança, afinal.
Martin Maat
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.