Technical Debt – Cos’è?

E’ una metafora. In due parole, le scelte sub-ottimali di design (includendo nel termine design anche il codice) che abbassano la qualità di un sistema sono come debiti: se non controllati, porteranno al fallimento a causa degli interessi che generano. D’altra parte, un debito contratto con l’ottica di investire il capitale preso a prestito generando un utile, costituisce un prezioso strumento nelle mani di qualsiasi imprenditore.

La metafora è attribuita a Ward Cunningham, che la spiega diffusamente in questo video. La sua natura economica fa si che sia molto apprezzata dal management.

Partendo dalla definizione intuitiva si puo’ già trarre una prima conclusione di fondamentale importanza: il debito tecnico, di per sé, non è né buono né cattivo.

Per quanto illuminante, la metafora espressa nei termini sopra indicati non è molto “operabile”. Come misuro il debito che ho? Come misuro l’interesse che genera? Come decido se mi conviene indebitarmi un altro po’ o se non sia il caso di fare un piano di ammortamento, prima di trovarmi l’agenzia di recupero crediti alla porta?

Per rispondere a queste domande, ho trovato molto utile questa definizione (la trovate insieme a molto altro materiale interessante in questo libro):


Il debito tecnico è un insieme di scelte di design che, accumulandosi nel tempo, rendono il sistema difficile da manutenere e da far evolvere. Il debito tecnico è pertanto qualcosa che influenza negativamente le caratteristiche interne e non-funzionali di un sistema, in particolare la sua manutenibilità ed evolvibilità.

Il debito è quindi qualcosa che rende il nostro sistema più difficile da mantenere o da estendere. Quando facciamo scelte di design sub-ottimali, contraiamo il debito. Ogni volta che dovremo modificare il software, è probabile che dovremo impiegare più risorse di quelle che avremmo impiegato se avessimo fatto la scelta ottima: ecco l’interesse che si manifesta. Per smettere di pagare l’interesse, è necessario rivedere l’iniziale scelta “quick and dirty”, accettando di ripagare il capitale preso in prestito.

Un esempio pratico

Facciamo un esempio: supponiamo dover aggiungere una feature (Feature A) ad un sistema. Dopo esserci scervellati, individuiamo una soluzione, elegante e flessibile, la cui implementazione quantifichiamo in 15 giorni (uso giorni per semplicità: ai fini dell’esempio l’unità di stima è irrilevante).

Il product owner tuttavia ha un disperato bisogno di avere la feature pronta per una demo imminente ad un potenziale cliente e ci chiede se sia possibile avere qualcosa alla svelta, qualsiasi cosa purchè funzioni. Dopo averci ragionato su, individuiamo una possibile soluzione scarsamente mantenibile ma implementabile in 5 giorni, in tempo per la demo. Decidiamo quindi di implementare la soluzione “quick and dirty”, contraendo un debito di 10 giorni (l’entità del debito potrebbe essere diversa dalla differenza tra le due opzioni, ma teniamo per ora le cose semplici)

La demo va alla grande e qualche giorno dopo il cliente firma un bel contratto che prevede, tra le altre cose, una feature aggiuntiva (Feature B). Il product owner chiede una stima di questa nuova feature, apparentemente piuttosto semplice. Analizzando la situazione, emerge che, a causa della soluzione quick and dirty implementata inizialmente, la modifica è tutt’altro che banale e richiede 10 giorni di lavoro. il product owner sviene.

A questo punto, si aprono due scenari: andare avanti a pagare gli interessi del debito (costo 10 contro un 5 teorico) oppure decidere di ripagare il capitale iniziale, rifattorizzando la soluzione quick in dirty in qualcosa di decente (costo totale 15, di cui 10 per il refactoring).

Cosa scegliere? La risposta è purtroppo: dipende. Vedremo meglio nel terzo post della serie come scegliere “cum grano salis” la strategia migliore.

Chiaro no? Su carta tutto semplice, ma la realtà è un po’ più complessa di così.

Quanti tipi ne esistono?

Cosí come il mutuo per la casa non è una carta di credito, allo stesso modo il debito tecnico ha varie forme. Le principali sono:

  • Quello nel codice: tipicamente impatta la manutenibilità, è facile da identificare anche tramite strumenti automatici.
  • Quello d’architettura: piú difficile da identificare, impatta solitamente l’evolvibilità.
  • Quello di infrastruttura: legato a build, deploy, test automation.

In comune hanno la loro natura di debito: rallentano l’evoluzione e la manutenzione del sistema. Altro elemento comune è la loro invisibilità ad un’osservatore esterno al team di sviluppo: l’aspetto visibile è dato dall’effetto sulla velocità o qualità.

Tipicamente il debito più pesante è quello di architettura: ripagarlo può’ significare rifattorizzare grosse fette di sistema… riscrivere da zero. L’equivalente di dichiarare bancarotta per eccesso di debiti.

Vediamo ora dove la metafora inizia a fare acqua.

Limiti della metafora

Debito volontario e involontario

Sebbene noi italiani non brilliamo per cultura finanziaria (interessante a questo proposito il report 2015 di S&P), difficilmente qualcuno contrae un debito senza rendersene conto. Puo’ magari farlo con troppa leggerezza, senza pensare alle reale sostenibilità, ma c’è sempre una scelta conscia dietro.

Lo stesso non si puo’ dire per il debito tecnico: nella mia esperienza, le scelte consce (“so che contraggo un debito”) sono l’eccezione. Le scelte consce e pesate (“so che contraggo un debito e so che mi conviene farlo”) sono ancora più rare.

Le ragioni sono diverse: l’inesperienza del team, la mancanza di documentazione o linee guida, turnover di risorse, cambiamenti di requisiti,… maggiori dettagli su questo nel prossimo post.

Non sempre genera interesse

Contrariamente ai debiti di natura economica, i debiti tecnici, talvolta, non generano interesse. Ma come è possibile?

La ragione è facilmente comprensibile partendo dalla definizione data sopra. Il debito riduce manutenibilità ed evolvibilità: un sistema che non evolve, non deve preoccuparsi degli interessi. Tornando all’esempio precedente, se la feature B non fosse stata necessaria, avremmo potuto tranquillamente tenerci il capitale preso in prestito senza preoccuparci delle conseguenze.

Non è facilmente quantificabile

Un debito economico è sempre quantificabile con precisione. Il debito tecnico molto meno.

Alcuni strumenti di analisi statica del codice come SonarQube forniscono dei valori numerici dell’effort, in giorni/uomo, necessario per ripagare il debito tecnico. Questi numeri tuttavia vanno presi con le pinze: nel migliore dei casi sono una stima di una parte del debito, quello più semplice legato a problemi nel codice (assenza di test unitari, complessità ciclomatica elevata, mancanza di commenti, etc…). Non dicono niente su eventuali lacune architetturali, su mancanze nella pipeline di CI/CD e su altri aspetti che, limitando la modificabilità, rientrano a buon diritto sotto la definizione di debito tecnico.

Attenzione quindi all’uso di questi dati. Personalmente preferisco ignorarli (il numero, non le segnalazioni sottostanti) perché possono trasmettere il messaggio sbagliato ad un pubblico non tecnico. Una quantificazione del debito è possibile, ma è un’attività di stima come un’altra.

Conclusioni

Il debito tecnico è un’utile metafora per rappresentare tutte le scelte, consce o involontarie, che riducono la manutenibilità e l’evolvibilità del codice (in parole povere, velocità e qualità). Dato che l’entropia aumenta in assenza di controllo, mantenere il debito ad un livello accettabile richiede perizia e impegno. D’altra parte, sapere quando tagliare una curva da un vantaggio competitivo a chi sa farlo in modo consapevole… senza finire in un fosso.