mercoledì 1 aprile 2009

La pericolosità dei metodi void

Generalmente non ci si pensa mai, ma un metodo void (inteso sia come tipo di ritorno che come lista di parametri) è di fatto più difficile da controllare che un metodo con una lista di parametri e un tipo di ritorno.
Per comprendere meglio, si consideri un metodo con il seguente prototipo:

public int somma(int op1, int op2);

ora, il metodo accetta in ingresso due operandi (op1, op2) e li trasforma (li somma) per fornire un risultato in uscita. In sostanza il metodo opera come una funzione applicata al dominio degli operandi op1, op2 e produce un risultato nel codominio. Supponiamo che la funzione abbia una implementazione come segue:

public int somma(int op1, int op2){
this.somma = op1 + op2;
return this.somma;
}

e si supponga che il prototipo del metodo cambi in modo da non ritornare il risultato dell'applicazione della funzione:

public void somma(int op1, int op2);

Chiaramente il metodo compie ancora il suo scopo (salvare in una variabile interna ad un oggetto la somma), ma ciò potrebbe non essere chiaro al chiamante per via dell'incapsulamento. Si ha quindi un side-effect: il programma cambia il suo stato senza che ciò sia direttamente visibile.
Perché questo è problematico? Perché senza un debugger, senza introspezione, oppure senza una dichiarazione volontaria da parte dell'oggetto circa la sua mutazione di stato, quest'ultima risulterà invisibile al mondo esterno, e quindi difficile da comprendere. Ci si trova quindi in una condizione in cui è difficile, dal lato del chiamante, verificare se il metodo abbia realmente applicato la funzione, e dove sia stato memorizzato il risultato. Se da un lato questo è alla base dell'incapsulamento (i dati mascherati dalle operazioni), dal lato pratico rende più complesso analizzare e controllare il codice, e ciò potrebbe causare problemi in sistemi mission-critical.
Si supponga ad esempio che la funzione, a seguito di un cattivo aggiornamento, abbia la seguente implementazione:

public void somma(int op1, int op2){
this.somma += op1 + op2;
}

ovvero la variabile interna somma non memorizza più una reale somma ma l'accumulazione degli operandi. Come potrebbe un chiamante accorgersene? Se l'incapsulamento è buono, semplicemente non può accorgersene!

E' per questo anche che esistono le unit-test, e che appunto in casi come quelli qui sopra i test vengono svolti da classi nello stesso package o che comunque hanno accesso anche allo stato inerno. Ma le unit-test sono solo controlli, non risolvono il problema alla radice. Il problema in questo caso è che i mutators dovrebbero sempre fornire al chiamante un risultato che rispecchi la mutazione di stato. Non sempre è possibile, non sempre è conveniente, non sempre è comodo. Ma quando possibile, ritornare il valore dell'applicazione di un metodo consente di scrivere codice adatto a situazioni mission-critical. Non solo: i metodi che ritornano dei risultati permettono anche di fare un chain degli stessi, con il risultato di codice più elegante e compatto (si pensi alle API NIO).

Come sempre è necessario trovare il giusto bilanciamento fra ottimizzazione del codice e sicurezza dello stesso.

Nessun commento: