Mostri sacri che sanno fare dell’autocritica

Questa e’ una delle caratteristiche che amo tra i veri grandi. La capacita’ di criticare le proprie azioni e il proprio pensiero, o anche cestinare per intero un sogno, con semplicita’, senza maschere.
Spesso capita di assistere a lunghe autocritiche psicologiche, un po’ new-age, che in verita’ sono solo apologie prive di costrutto.

Tom De Marco (che ha scritto Peopleware, un libro brillante) ha recentemente criticato con grande franchezza l’approccio “misura-e-controlla” nell’ingegneria del software, da lui stesso promosso vigorosamente per molti anni. Merita leggerlo, come ogni autocritica vera, non si nasconde dietro a un atteggiamento colpevole, ma un sincero “A non era giusto per questo motivo, B invece era giusto per quest’altro motivo, in fine ormai sono convinto di C”.

Lo stesso si puo’ dire dell’analisi di Kent Beck sulla sua scelta di smettere lo sviluppo di JUnit Max che io personalmente non condivido, ma lui ha sicuramente il punto di vista piu’ completo della situazione.
In particolare ammiro come, dopo aver rinunciato a quello che lui stesso definisce un sogno, ancora caldo di questo “fallimento”, dopo che gli e’ stato fatto osservare che forse non aveva spinto correttamente sul marketing del prodotto, non ha indugiato a spiegare come no, il marketing non era debole, era ben calibrato : ha sbagliato a pensare che JUnit Max potesse avere una solida base di utenti, e l’ha detto, sul marketing non ha sbagliato, e gli andra’ meglio con un altro progetto.

Punto.

Questa e’ l’autocritica : si riconoscono gli errori di fondo e li si spiega, perche’ chi ha fatto l’errore e’ la persona piu’ indicata a identificarlo e spiegarlo.

Spesso noi comuni mortali interpretiamo l’analisi e la spiegazione dell’errore come una specie di giustificazione : “Guardalo li’, sempre a giustificarsi”.

Questo e’ dovuto al fatto che noi comuni mortali siamo abituati all’autocritica di scusa, quella con cui ci autoaccusiamo di quanto accaduto sperando di alleviarne il senso di colpa e in cui non cerchiamo con pensiero limpido le cause prime, cerchiamo piuttosto il solievo della confessione. Non c’e’ crescita nell’autoflagellazione, soltanto l’acquietarsi della propria coscienza, non per niente questo genere di confessione tende a ripetersi errore dopo errore.

In particolare la meccanica a cui siamo piu’ abituati e’ : io che sono in una posizione di debolezza e sbaglio devo confessarmi per rappacificare me e gli altri, chi e’ in posizione di forza, secondo questo criterio, non ha bisogno di alcuna confessione. Per questo, quando i grandi non si scusano, bensi’ si spiegano e fanno un altro passo in avanti, lo confondiamo per orgoglio.

Dovremmo invece tenerci in tasca i rimorsi di coscienza, per quanto scomodi siano, e cercare con tranquillita’ le cause prime e spiegarle con la maggiore precisione possibile, senza alcun fine di scusa. L’autoflagellazione e’ una forma di snobbismo : ci chiudiamo all’analisi e ci poniamo sul piedistallo del peccatore.

Di solito quando l’autocritica e’ una forma di confessione mascherata si capisce abbastanza facilmente : la confessione identifica rapidamente il problema nella persona.
-Mi sono distratto, devo stare piu’ attento-
-Non sono abbastanza bravo-
-Dovevo lavorare di piu’-

Quando il problema identificato risiede nella persona allora l’autocritica (o la critica) mi puzza di flagellazione e buoni propositi.
Darsi la colpa e’ solo una varianzione sul tema di dare la colpa a qualcunaltro, serve solo a convincerci di aver “fatto qualcosa” per rimediare al problema, senza essersi veramente dati la pena di cercare il problema. Una persona puo’ fare un errore, o molti, ma, se abbiamo fiducia in quella persona e’ molto piu’ interessante scoprire cosa ha causato quell’errore, veramente. Cercando la causa prima, senza distribuire colpe inutilmente, per poi lavorare per costruire un contesto che minimizzi tali cause d’errore o che le renda ininfluenti.

Dopo esserci dimenticati di aggiungere una variabile al lancio di uno script che ha mandato all’aria un giorno di lavoro dovremmo essere molto piu’ interessati a spiegare come togliere quella variabile e rendere l’errore impossibile, mentre invece ci concentriamo sul dettaglio morboso delle dinamiche che ci hanno portato alla fatale distrazione.

I grandi che hanno timore di essere giudicati si comportano come noi comuni mortali : si autoflagellano quando devono e si negano quando possono. I grandi, quelli veri, mettono in discussione gli assunti che li hanno portati a sbagliare, il piu’ possibile dritti al punto, senza timori.

Mi piacerebbe imparare ad analizzare il fallimento di un mio sogno con la tranquillita’ intellettuale di Kent che abbandona JUnit Max, senza cercare la mia colpa, ma solo cercando l’errore, ovunque sia, senza scuse, con sincerita’ scientifica.

Advertisements

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.