Degenerazioni

Come, spingendo sui patterns e sui principi object-oriented si giunge a costruire del codice perfettamente procedurale.

Forse un anno fa sulla lista di xp-it qualcuno aveva scritto un post che diceva piu’ o meno cosi’ :
“Nel sistema che stiamo sviluppando ci sono voluti giorni interi per identificare quale interfaccia implementare per passare ad avere una diversa gestione delle sessioni, ma poi sono bastati cinque minuti a scrivere l’implementazione, senza dover modificare niente altro…”

Ricordo che ai tempi osservai qualcosa di questo genere “Ho idea che si sia fatto un gran lavoro di isolamento dei concerns, ma che non si sia costruita una metafora del sistema”. Questo genere di situazione lo vedo nascere quando, invece di concentrarsi sull’esplicitare i concetti del dominio del problema si ci concentra sull’applicazione di patterns e principi di design da un punto di vista puramente “meccanico”.

Che cosa intendo per approccio “meccanico”?

Intendo il fenomeno per il quale, identificata della logica condizionale nel codice la prima cosa che si fa e’ iniziare a riflettere su come eliminarlo utilizzando uno dei meccanismi messi a disposizione dai linguaggi OO.
Ad esempio, questo :

for(String name : names) {
Account account = retrieveAccountByName(name);
account.fooTheBars();
}

Diventa subito questo :

names.forEach(new FooTheAccountBars());

Questo :

if(type == "CREDIT") {
sendMailTo("pippo@pippo.com");
debitAmount = 0;
}
if(type == "DEBIT") {
debitAmount = amount;
}

Si trasforma in questo :

accountType.perform();

E cosi’ via.
Queste trasformazioni tendono a rispecchiare dei patterns conosciuti (una caramella a chi riconosce il pattern del primo esempio) e a seguire i valori di modularita’, sostituibilita’ e isolamento tanto cari al design object oriented (e non solo). Lo dimostrano le metriche, che saranno certamente migliori di quelle del codice originale, anzi, potranno giungere ad essere davvero ottime : “Guarda mamma, senza if!”

Software costruito in questo modo avra’ molte o tutte le caratteristiche meccaniche di un sistema object oriented, ma gli manchera’ il bagaglio informativo.

Trovo che gli elementi che nascono da un tale design non descrivano il dominio del problema da risolvere, piuttosto rappresentano, ad oggetti, la soluzione procedurale al problema.

Faccio un altro esempio (non lontano da codice che ho scritto io allegramente) :

Door door = new Door("123019");
....
RequestReceiver receiver = new StreamLineSplittingReceiver(new XmlParsingReceiver(new FilteringReceiver("comment"), new DoorRequestReceiver(door))));
.....
DoorStatus currentStatus = receiver.receive(xmlStream);

Questo codice descrive chiaramente la soluzione procedurale : quando ricevo una richiesta per una serratura, prima estraggo l’xml dallo stream, poi lo parso, poi filtro via i commenti e infine reagisco alla richiesta.

Fallisce miseramente invece nel descrivere i concetti di base del sistema.


Door door = new Door("1230401");
....
KeyOperationBuilder builder = new XmlKeyOperationBuilder(new CommentsFilter("comment));
XmlSource xmlSource = new MultilineXmlSource(stream);
....
....
String xml = xmlSource.nextXml();
KeyOperation keyOperation = builder.buildKeyOperationFrom(xml);
keyOperation.operateOn(door);
DoorStatus currentStatus = door.status();

Questo secondo codice non utilizza meccanismi OO per comporre la procedura; usa gli oggetti per rappresentare i concetti e una procedura per rappresentare la loro interazione.

La catena di decorazioni di prima e’ quello che sto giungendo a considerare un virtuosismo, nel senso negativo del termine : un’ostentazione fine a se stessa. Usare la composizione di molti oggetti per rappresentare una sequenza di chiamate qualunque non porta a una migliore esplicitazione del dominio del problema e quindi non porta un vero vantaggio di semplificazione concettuale.

Sostituire un if con una strategia non porta necessariamente alla costruzione di un’astrazione significativa per il nostro dominio e lo stesso si puo’ dire di moltissime trasformazioni che, per quanto migliorino le metriche, sono solo virtuosismi meccanici.

Trovo che proprio nel refactoring di codice legacy o nell’esecuzione in tdd di uno nuovo sviluppo sia facile cadere in questo genere di virtuosismi. Personalmente mi piace -non- pensare mentre scrivo la soluzione a un test rosso. Buona parte del pensiero avviene mentre scrivo il test. Il guaio e’ che si puo’ cominciare a non pensare anche mentre si rifattorizza.

Una volta che si e’ praticato il refactoring abbastanza a lungo si puo’ giungere ad applicarlo automaticamente, come riflesso condizionato. Di fatto il pensiero si ferma a : c’e’ un if -> rimuovo l’if con… e le mani partono da sole a lavorare.

Questa e’ una degenerazione della pratica del refactoring e degli strumenti che il paradigma ad oggetti offre. All’identificazione di uno smell si ci dovrebbe interrogare sulle sue cause prime, in particolare se per caso quello smell non nasca dalla necessita’ di approfondire la nostra rappresentazione del problema con nuovi e diversi concetti.

La verita’ e’ che buona parte dei difetti meccanici (e quindi identificabili tramite delle metriche) del codice procedurale sono risolvibili costruendo con gli oggetti delle funzioni di ordine maggiore.

Quasi ogni variazione del comportamento durante il processo di sviluppo del software e’ gestibile con un altro strato di indirezione.

In questo modo pero’, quello che avviene e’ che si reifica la procedura, non il dominio del problema.

Sono i concetti latenti dietro una soluzione (gia’ implementata e in corso di rifattorizzazione, o solo disegnata sulla lavagna o nella mente di qualcuno) che hanno bisogno di essere resi espliciti e concreti nella forma di un oggetto.

Ho scritto migliaia di linee di codice che rispecchiassero al meglio incapsulamento, sostituibilita’, segregazione e compagnia bella, ottenendo risultati non esplosivi che hanno permesso al sistema di procedere sotto controllo, ma le uniche occasioni in cui ho potuto riprendere in mano del codice sentendo di poterlo portare a un nuovo livello di funzionalita’ senza alcuno sforzo e’ stato quando quel codice rappresentava delle storie : nel codice che amo davvero ci sono figli che raccolgono l’eredita’ dei padri (addebiti diretti bancari con possibilita’ di rifiuto dell’addebito anche dopo mesi), conversazioni che si compongono di altre conversazioni (IO di un sistema di pagamenti), dottori con le loro sonde (diagnostica) e cosi’ via.

I nomi non devono essere necessariamente fuori dal contesto per dimostrare la presenza di una metafora forte; anzi, i nomi evocativi spesso sono solo un modo per mascherare la debolezza del modello che si e’ scelto.
Cio’ che conta e’ come la metafora dia significato alla distribuzione delle responsabilita’, e’ come il dominio fornisca livelli sempre piu’ alti di astrazione del problema (e non parlo di classi astratte). Di solito, trovato il modello che meglio descrive il problema si ha la strana impressione di aver perso un sacco di tempo nell’arrovellarsi per trovare una soluzione che era evidente sin da subito. Mio nonno in carriola. La soluzione finale dell’esempio del bancomat della Wirfs-Brock parla di tastiere, scontrini e schermi, ma non per questo basta parlare da subito di tastiere, scontrini e schermi per ottenere un buon design.

Parlare del problema usando gli oggetti che veramente lo definiscono e semplificano, scoprire queste storie dietro a un problema che percepiamo come una sequenza di operazioni, e’ sempre difficile; e’ qualcosa che mi riesce saltuariamente, ma che trovo valga la pena cercare con costanza.

Questa ricerca virtuosa, a cui e’ dedicato DDD e esemplificata in questo post di Matteo, e’ l’estremo opposto del fenomeno pernicioso che ho descritto in questo post, che porta a usare degli oggetti e interi patterns per descrivere delle procedure invece che il contrario.
L’ho osservato per molto tempo, notando il degenerare dell’uso degli oggetti mano a mano che si cessava di cercare il significato e si ci concentrava solo nel gestire l’espandersi degli assi di variazione del sistema e l’ho discusso veramente per la prima volta con un collega in cui riflettevamo su come si possano usare i patterns basati sulla composizione per descrivere un algoritmo arbitrario e di quanto questo sia dannoso.

Concludo chiedendomi se, immaginando di trovare con affidabilita’ il giusto modello di ogni problema, potremmo davvero fare a meno di interrogarci su tutti i temi che, nell’ingegneria del software, in particolare la rottura delle dipendenze, ossessionano i principi di design a oggetti.

Advertisements

2 thoughts on “Degenerazioni

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s