L'uso di tipi nativi ( int
, Guid
, ecc.) invece di tipi che esprimono l'intento ( Price
, ProductIdentifier
, ecc.) ha due svantaggi :
-
La modifica del tipo è sovrapposta a tutto il codice base , il che la rende particolarmente dolorosa.
Ad esempio, passare da smallint
( short
) a uniqueidentifier
( Guid
) non è solo doloroso a livello di database (come si trasformano i dati di produzione da campi auto-incrementati a UID generati casualmente?) , ma anche a livello di codice. Devi passare attraverso l'intero codice base e sostituire short
di Guid
, eventualmente trattare con errori di compilazione o persino errori di runtime (in un caso di linguaggio dinamico o quando viene usato Reflection).
-
I metodi relativi al tipo sono diffusi attraverso il codice base invece di essere vincolati all'interno di una singola classe. Questo crea un rischio di duplicazione del codice .
Quando hai ProductIdentifier
, puoi facilmente individuare il metodo CheckIfValueInRange
: ovviamente, è in ProductIdentifier
class e solo lì.
Quando hai int
come identificatore del prodotto, puoi indovinare che CheckIfValueInRange
è nel livello di accesso ai dati, ma poi spetta a te individuarlo. Sotto pressione, alcuni programmatori potrebbero essere riluttanti a passare il tempo a cercare il metodo e scrivere il proprio correttore:
if (this.Id <= 0 || this.Id > 100000)
{
throw new Exception("The product ID is invalid.");
}
che potrebbe sfortunatamente differire da un'altra implementazione che è più conforme alle specifiche:
const int MAX_ID = 100000;
if (this.Id < 0 || this.Id > MAX_ID)
{
throw new OutsideRangeException("The identifier is outside the allowed range.");
}
che porta a un incubo di refactoring / manutenzione, che potrebbe essere facilmente evitato se si stesse utilizzando la classe di ProductId
.
Gli identificatori cambiano spesso il loro tipo?
In realtà, lo sono. Uno sviluppatore decide di utilizzare smallint
per un sito Web di piccole dimensioni; il sito Web diventa estremamente popolare e una volta raggiunto il limite di 32.767 valori, il tipo deve essere modificato. Un altro sviluppatore utilizza uniqueidentifier
, e successivamente sembra che non ci siano vantaggi per questo tipo nel contesto reale, ma avere un valore auto-incrementato, ordinato sarebbe bello.
Ciò rende gli identificatori un buon candidato per l'utilizzo di un tipo personalizzato anziché nativo.
Gli identificatori hanno una logica aziendale?
A parte il controllo dell'intervallo (vedi sopra), ci sono molti casi in cui ha senso collegare la logica aziendale all'identificatore stesso:
-
Generazione di un nuovo ID,
-
Verifica dell'esistenza di un ID,
-
Controllare se l'ID appartiene a un gruppo specifico (come l'ID da 1 a 10000 è il gruppo A e gli ID superiori a 10000 sono il gruppo B),
-
ecc.
Si noti che rimuovendo l'astrazione che perde l'ORM attraverso l'uso di un tipo personalizzato, si crea anche una nuova astrazione che perde. Immagina di voler ordinare qualcosa per ID. Se ProductId
corrisponde a un campo auto-incrementato, ha senso. Se ProductId
corrisponde a un GUID, l'ordinamento non ha senso e dovresti cercare un criterio diverso, come Product.CreatedTimeUtc
.