Quando l'ossessione primitiva non è un odore di codice?

14

Ho letto molti articoli recentemente che descrivono Primitive Obsession come odore di codice.

Ci sono due vantaggi nell'evitare l'ossessione primitiva:

  1. Rende il modello di dominio più esplicito. Ad esempio, posso parlare con un analista aziendale di un codice postale anziché di una stringa che contiene un codice postale.

  2. Tutta la convalida è in un posto anziché nell'intera applicazione.

Ci sono molti articoli là fuori che descrivono quando si tratta di un odore di codice. Ad esempio, posso vedere il vantaggio di rimuovere l'ossessione primitiva per un codice postale come questo:

public class Address
{
    public ZipCode ZipCode { get; set; }
}

Ecco il costruttore del codice postale:

public ZipCode(string value)
    {
        // perform regex matching to verify XXXXX or XXXXX-XXXX format
        _value = value;
    }

Romperebbe il principio DRY mettendo la logica di validazione ovunque sia utilizzato un codice postale.

Tuttavia, per quanto riguarda i seguenti oggetti:

  1. Data di nascita: verifica che la data sia maggiore di mente e inferiore alla data odierna.

  2. Salario: controlla che sia maggiore o uguale a zero.

Vuoi creare un oggetto DateOfBirth e un oggetto Stipendio? Il vantaggio è che puoi parlarne quando descrivi il modello di dominio. Tuttavia, si tratta di un caso di over engineering in quanto non vi è molta convalida. C'è una regola che descrive quando e quando non rimuovere l'ossessione primitiva o dovresti farlo sempre se possibile.

Aggiorna Credo che potrei creare un alias di tipo invece di una classe, il che aiuterebbe con il punto uno sopra.

    
posta w0051977 31.01.2018 - 11:14
fonte

6 risposte

9

Primitive Obsession is using primitive data types to represent domain ideas.

L'opposto sarebbe "modellazione del dominio", o forse "sovrascrittura".

Would you create a DateOfBirth object and a Salary object?

L'introduzione di un oggetto Salary può essere una buona idea per il seguente motivo: i numeri raramente si trovano da soli nel modello di dominio, hanno quasi sempre una dimensione e un'unità. Normalmente non modelliamo nulla di utile se aggiungiamo una lunghezza a un tempo o una massa e raramente otteniamo buoni risultati quando mescoliamo metri e piedi.

Come per DateOfBirth, probabilmente - ci sono due problemi da considerare. In primo luogo, la creazione di una data non primitiva ti offre un posto in cui concentrare tutte le strane preoccupazioni relative alla matematica delle date. Molte lingue ne forniscono una pronta all'uso; DateTime , java.util.Date . Queste sono le implementazioni agnostiche del dominio delle date, ma sono non primitive.

Secondo, DateOfBirth non è realmente una data; qui negli Stati Uniti, "data di nascita" è un costrutto culturale / finzione legale. Tendiamo a misurare la data di nascita dalla data locale della nascita di una persona; Bob, nato in California, potrebbe avere una data di nascita "precedente" rispetto ad Alice, nata a New York, anche se è il più giovane dei due.

Is there a rule that describes when and when not to remove primitive obsession or should you always do it if possible.

Certamente non sempre; ai limiti, le applicazioni non sono orientate agli oggetti . È abbastanza comune vedere le primitive utilizzate per descrivere i comportamenti in test .

    
risposta data 31.01.2018 - 13:25
fonte
6

Per essere onesti: dipende.

C'è sempre il rischio di sovrastimolare il tuo codice. Quanto sarà diffuso il DateObBirth e lo Stipendio? Li userete solo in tre classi strettamente accoppiate, o saranno usati in tutta l'applicazione? Vorresti "solo" incapsularli nel loro Type / Class per far rispettare quel vincolo, o potresti pensare a più vincoli / funzioni che effettivamente ci appartengono?

Prendiamo ad esempio lo stipendio: hai qualche operazione con "Salary" (ad esempio gestendo diverse valute, o forse una funzione toString ())? Considera cosa è / è lo stipendio quando non lo guardi come una semplice primitiva, e c'è una buona possibilità che Salary sia la sua classe.

    
risposta data 31.01.2018 - 11:51
fonte
5

Una possibile regola empirica può dipendere dal livello del programma. Per il Dominio (DDD) alias Livello Entità (Martin, 2018), potrebbe anche essere "evitare primitive per qualsiasi cosa rappresenti un dominio / concetto di business". Le giustificazioni sono quelle indicate dall'OP: un modello di dominio più espressivo, la convalida delle regole di business, rendendo espliciti i concetti impliciti (Evans, 2004).

Un alias di tipo può essere un'alternativa leggera (Ghosh, 2017), e refactored ad una classe di entità quando necessario. Ad esempio, potremmo in primo luogo richiedere che Salary sia >=0 , e successivamente decidere di disabilitare $100.33333 e qualsiasi valore superiore a $10,000,000 (che farebbe bancarotta il client). L'uso di Nonnegative primitive per rappresentare Salary e altri concetti complicherebbe questo refactoring.

Evitare le primitive può anche aiutare a evitare la sovraingegneria. Supponiamo di dover combinare Salary e Data di nascita in una struttura di dati: ad esempio, per avere un minor numero di parametri di metodo o per passare i dati tra i moduli. Quindi possiamo usare una tupla con tipo (Salary, DateOfBirth) . In effetti, una tupla con i primitivi, (Nonnegative, Nonnegative) , non è efficace, mentre alcuni% co_de gonfiati nascondono i campi obbligatori, tra gli altri. La firma diciamo class EmployeeData è più focalizzata che in calcPension(d: (Salary, DateOfBirth)) , che viola il principio di segregazione dell'interfaccia. Allo stesso modo, un calcPension(d: EmployeeData) specializzato sembra imbarazzante ed è probabilmente eccessivo. Più tardi, potremmo scegliere di definire una classe di dati; tuple e tipi di dominio elementale ci permettono di rinviare tali decisioni.

In uno strato esterno (ad es. GUI) può avere senso "spogliare" le entità verso i loro primitivi costituenti (es. per inserirli in un DAO). Ciò impedisce la dispersione delle astrazioni del dominio nei livelli esterni, come sostenuto in Martin (2018).

Riferimenti
E. Evans, "Domain-Driven Design", 2004
D. Ghosh, "Modellazione del dominio funzionale e reattiva", 2017
R. C. Martin, "Architettura pulita", 2018

    
risposta data 02.02.2018 - 21:56
fonte
3

Se hai avuto una classe di stipendio potrebbe avere metodi come ApplyRaise.

D'altro canto, la classe ZipCode non deve avere una convalida interna per evitare la duplicazione della convalida ovunque si possa avere una classe ZipCodeValidator che potrebbe essere iniettata, quindi se il proprio sistema deve essere eseguito sia negli Stati Uniti che nel Regno Unito è possibile inserisci il validatore corretto e quando devi gestire anche gli indirizzi AUS, puoi semplicemente aggiungere un nuovo validatore.

Un'altra preoccupazione è che se si devono scrivere dati in un database tramite EntityFramework, allora sarà necessario sapere come gestire lo stipendio o il codice postale.

Non esiste una risposta chiara su dove tracciare la linea di demarcazione tra le classi intelligenti, ma dirò che tendo a spostare la logica aziendale, come la convalida, a classi di business logic che hanno le classi di dati come dati puri poiché sembra funzionare meglio con EntityFramework.

Per quanto riguarda l'utilizzo di alias di tipo, il nome membro / proprietà dovrebbe fornire tutte le informazioni necessarie sui contenuti, quindi non utilizzerei alias di tipi.

    
risposta data 31.01.2018 - 11:33
fonte
3

Meglio soffrire di Obsession Primitive o essere Architecture Astronaut ?

Entrambi i casi sono patologici, in un caso si hanno troppe poche astrazioni, che portano alla ripetizione e si confondono facilmente una mela con un'arancia, e nell'altra hai dimenticato di fermarti e iniziare a fare le cose, rendendo difficile fare qualcosa.

Come quasi sempre, vuoi la moderazione, una via di mezzo speranzosamente ben ponderata.

Ricorda che una proprietà ha un nome, oltre a un tipo. Inoltre, la scomposizione di un indirizzo nelle sue parti costitutive potrebbe essere troppo restrittivo se fatto sempre nello stesso modo. Non tutto il mondo è nel centro di New York.

    
risposta data 02.02.2018 - 22:47
fonte
0

(La domanda probabilmente è davvero

Quando l'uso del tipo primitivo non è un odore di codice?

(risposta)

Quando il parametro non ha regole in esso - usa un tipo primitivo.

Usa il tipo primitivo per: Mi piace

htmlEntityEncode(string value)

Utilizza l'oggetto per: Mi piace

numberOfDaysSinceUnixEpoch(SimpleDate value)

L'ultimo esempio contiene regole, ad esempio, l'oggetto SimpleDate è composto da Year , Month e Day . Attraverso l'uso di Object in questo caso, il concetto di SimpleDate è valido può essere incapsulato all'interno dell'oggetto.

    
risposta data 20.06.2018 - 06:09
fonte