Quali sono gli svantaggi di mappare identificatori integrali su enum?


16

Ho pensato di creare tipi personalizzati per identificatori come questo:

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

La mia motivazione principale per questo è prevenire il tipo di bug in cui si passa accidentalmente un orderItemId a una funzione che si aspettava un orderItemDetailId.

Sembra che gli enum funzionino perfettamente con tutto ciò che vorrei usare in una tipica applicazione Web .NET:

  • Il routing MVC funziona correttamente
  • La serializzazione JSON funziona correttamente
  • Ogni ORM a cui riesco a pensare funziona bene

Quindi ora mi chiedo "perché non dovrei farlo?" Questi sono gli unici inconvenienti che mi vengono in mente:

  • Potrebbe confondere altri sviluppatori
  • Introduce incoerenze nel sistema in caso di identificatori non integrali.
  • Potrebbe richiedere un casting extra, come (CustomerId)42. Ma non penso che questo sarà un problema, dal momento che il routing ORM e MVC in genere ti consegnerà direttamente i valori del tipo enum.

Quindi la mia domanda è: cosa mi sto perdendo? Questa è probabilmente una cattiva idea, ma perché?



3
Questo si chiama "microtyping", puoi cercare su Google (ad es. Questo è per Java, i dettagli sono diversi, ma la motivazione è la stessa)
qbd

Ho scritto due risposte parziali e poi le ho cancellate perché mi sono reso conto che stavo pensando a questo errore. Concordo con te sul fatto che "Questa è probabilmente una cattiva idea, ma perché?"
user2023861

Risposte:


7

È un'ottima idea creare un tipo per ogni tipo di identificatore, principalmente per i motivi indicati. Non lo farei implementando il tipo come enum perché renderlo una corretta struttura o classe ti dà più opzioni:

  • È possibile aggiungere la conversione implicita a int. Quando ci pensi, è l'altra direzione, int -> CustomerId, quella problematica che merita una conferma esplicita da parte del consumatore.
  • È possibile aggiungere regole aziendali che specificano quali identificatori sono accettabili e quali no. Non si tratta solo di verificare se l'int è positivo: a volte, l'elenco di tutti gli ID possibili viene conservato nel database, ma cambia solo durante la distribuzione. Quindi puoi facilmente verificare che l'ID dato esista rispetto al risultato memorizzato nella cache della query corretta. In questo modo, puoi garantire che la tua applicazione fallirà rapidamente in presenza di dati non validi.
  • I metodi sull'identificatore possono aiutarti a eseguire costosi controlli di esistenza DB o ottenere l'oggetto entità / dominio corrispondente.

Puoi leggere su come implementarlo nell'articolo C # funzionale: ossessione primitiva .


1
Direi che quasi certamente in modo non si vuole avvolgere ogni int in una classe, ma in una struttura
JK.

Buona osservazione, ho aggiornato leggermente la risposta.
Lukáš Lánský,

3

È un trucco intelligente, ma è comunque un trucco che abusa di una funzionalità per qualcosa che non è lo scopo previsto. Gli hack hanno un costo in termini di manutenibilità, quindi non è sufficiente non trovare altri inconvenienti, ma devono anche avere vantaggi significativi rispetto al modo più idiomatico per risolvere lo stesso problema.

Il modo idiomatico sarebbe quello di creare un tipo di wrapper o una struttura con un singolo campo. Questo ti darà la stessa sicurezza in un modo più esplicito e idiomatico. Mentre la tua proposta è certamente intelligente, non vedo alcun vantaggio significativo nell'uso dei tipi di wrapper.


1
Il vantaggio principale di un enum è che "funziona" nel modo desiderato con MVC, serializzazione, ORM, ecc. Ho creato tipi personalizzati (strutture e classi) in passato e finisci per scrivere più codice di te dovrebbe essere necessario per far funzionare quei framework / librerie con i tuoi tipi personalizzati.
default.kramer

La serializzazione dovrebbe funzionare in modo trasparente in entrambi i modi, ma ad es. Gli ORM sono necessari per configurare un tipo di conversione esplicita. Ma questo è il prezzo di un linguaggio fortemente tipizzato.
Jacques B

2

Dal mio punto di vista, l'idea è buona. L'intenzione di fornire tipi diversi a classi di identificatori non correlate, e di esprimere in altro modo la semantica tramite tipi e lasciare che il compilatore lo controlli, è buona.

Non so come funziona in .NET dal punto di vista delle prestazioni, ad esempio i valori enum sono necessariamente inscatolati. Il codice OTOH che funziona con un database è comunque probabilmente associato a I / O e gli identificatori inscatolati non avranno alcun collo di bottiglia.

In un mondo perfetto, gli identificatori sarebbero immutabili e comparabili solo per l'uguaglianza; i byte effettivi sarebbero un dettaglio di implementazione. Gli identificatori non correlati avrebbero tipi incompatibili, quindi non potresti usarne uno per errore. La tua soluzione si adatta praticamente al conto.


-1

Non puoi farlo, gli enum devono essere predefiniti. Quindi, a meno che tu non sia disposto a pre-definire l'intero spettro di possibili valori degli ID cliente, il tuo tipo sarà inutile.

Se lanci, sfideresti il ​​tuo obiettivo principale di impedire l'assegnazione accidentale di un ID di tipo A a un ID di tipo B. Quindi gli enum non sono utili al tuo scopo.

Ciò solleva tuttavia la domanda se sarebbe utile creare i tuoi tipi per ID cliente, ID ordine e ID prodotto scendendo da Int32 (o Guid). Mi viene in mente un motivo per cui questo sarebbe sbagliato.

Lo scopo di un ID è l'identificazione. I tipi integrali sono già perfetti per questo, non ottengono più l'identificazione discendendo da loro. Non è richiesta né desiderata la specializzazione, il tuo mondo non sarà rappresentato meglio avendo tipi diversi di identificatori. Quindi dal punto di vista OO sarebbe male.

Con il tuo suggerimento vuoi qualcosa di più della sicurezza del tipo, vuoi la sicurezza del valore e questo va oltre il dominio della modellazione, questo è un problema operativo.


1
No, gli enum non devono essere predefiniti in .NET. E il punto è che il casting non accade quasi mai, per esempio in public ActionResult GetCustomer(CustomerId custId)nessun cast è necessario: il framework MVC fornirà il valore.
default.kramer

2
Non sono d'accordo Per me, ha perfettamente senso dal punto di vista OO. Int32 non ha semantica, è di tipo puramente "tecnico". Ma ho bisogno di un tipo di identificatore astratto che serva allo scopo di identificare gli oggetti e che capiti di essere implementato come Int32 (ma potrebbe essere lungo o altro, non importa davvero). Abbiamo specializzazioni concrete come ProductId che discendono dall'identificatore che identificano l'istanza concreta di ProductId. Non ha alcun senso logico assegnare il valore di OrderItemId a ProductId (è chiaramente un errore), quindi è fantastico che il sistema di tipi possa proteggerci da questo.
qbd

1
Non sapevo che C # fosse così flessibile riguardo all'uso degli enum. Certamente mi confonde come sviluppatore abituato al fatto (?) Che le enumerazioni sono, beh, enumerazioni di possibili valori. In genere specificano un intervallo di ID denominati. Quindi ora questo tipo viene "abusato", nel senso che non c'è intervallo e non ci sono nomi, solo il tipo rimane. E a quanto pare, il tipo non è neanche così sicuro. Un'altra cosa: l'esempio è ovviamente un'applicazione di database e ad un certo punto il tuo "enum" verrà mappato su un campo di tipo integrale in quel momento, dopo tutto, perderai il tuo tipo presunto in modo sicuro.
Martin Maat,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.