mercoledì 12 settembre 2012

Perché evitare l'uso dei tipi primitivi?

Normalmente quando definiamo un metodo, rischiamo di definire (i.e. per pigrizia) i parametri con dei tipi primitivi. 

Facciamo un esempio: 

public double CalcolareIlPrezzoScontato(double prezzo, double sconto)
{
   // implementare il corpo del metodo
}

Commentiamo la signature del metodo: 
  • il metodo utilizza tipi primitivi che hanno significati diversi; 
  • solo il nome dei parametri evidenzia il loro significato;
  • intuiamo dal nome del metodo che il valore di ritorno è un "prezzo"; 
  • potrei passare al parametro prezzo lo sconto e viceversa;
Proviamo a scrivere il metodo come segue: 

public double X(double y, double z)
{
   // implementare il corpo del metodo
}

Si si, concordo con voi, il metodo così definito non è molto chiaro, però è esattamente lo stesso metodo. 

Quindi cosa possiamo fare per:
  • migliorare la qualità del nostro codice? 
  • renderlo più leggibile? 
  • evitare errori banali? 
Il metodo CalcolareIlPrezzoScontato lavora con due concetti: 
  • prezzo;
  • sconto;
Bene, perché non tramutare questi due concetti in qualcosa di concreto? 

public class Prezzo
{
   public double Value { get; set; }
}

public class Sconto
{
   public double Value { get; set; }
}

Ora proviamo a ridefinire il metodo:

public Prezzo X(Prezzo y, Sconto z)
{
   // implementare il corpo del metodo
}

Sembra evidente che il metodo "X" prende in ingresso un Prezzo e uno Sconto e restituisce un Prezzo che possiamo ipotizzare scontato. Se vogliamo estremizzare il concetto potremmo creare una struttura PrezzoScontato che deriva da Prezzo.

public class PrezzoScontato : Prezzo
{
}

Quindi il metodo si può mutare in:

public PrezzoScontato X(Prezzo y, Sconto z)
{
   // implementare il corpo del metodo
}

Ora è evidente qual è il comportamento di "X". Questo ultimo passaggio può essere eccessivo, però è esattamente quello che il metodo CalcolareIlPrezzoScontato risolveva.

Inoltre facciamo un'ulteriore ipotesi e pensiamo che inizialmente i prezzi erano definiti come interi (software con valuta in lire) e anche lo sconto fosse di tipo intero (richiesta esplicita del product owner).

public int CalcolareIlPrezzoScontato(int prezzo, int sconto)

Poi un bel giorno la comunità Europea si inveta l'euro e noi (indovinate un po') dobbiamo modificare il nostro codice per sostituire da int a double tutti i punti dove esiste il concetto di prezzo. 

Adoro la funzionalità find / replace. 

Dopo qualche ora di sostituzioni manuali, incrociamo le dita, eseguiamo il programma.

"bhe!!! dai sembra funzionare... fino al primo crash :-)"

Mentre continuiamo a capire cosa abbiamo sbagliato, arriva il nostro adorato product owner e ci chiede: "ciao ragazzi, ho pensato che mentre fate il cambio di valuta da lire a euro, vorrei dare la possibilitá di inserire sconti anche con i decimali, tanto non dovrebbe essere molto complicato...".

"Bene bene, io mi licenzio."

Mentre il fantasma del "find/replace" aleggia nell'aria, nella testa aleggia un pensiero "come potevo evitare tutto questo?".

La risposta è facile, non dovevamo usare i tipi primitivi.

Infatti, se avessimo avuto le entità Prezzo e Sconto bastava cambiare il tipo della proprietà Value da int a double e tutto era sotto controllo.


Come potete vedere, l'uso di tipi primitivi per esprimere concetti noti può portare ai dei seri problemi.