Che cos’è il debito tecnico
Quando si prendono soldi in prestito da una banca o da un investitore, lo si fa perché si vuole fare qualcosa in maniera più rapida di quanto non si potrebbe fare altrimenti.
L’esempio più comune è quello dell’acquisto di una casa; immaginiamo che, per farlo, molti di voi avranno dovuto contrarre un mutuo.
Se aveste aspettato di poterla finanziare soltanto coi vostri risparmi, nella migliore delle ipotesi avreste dovuto aspettare la comparsa di qualche capello bianco; nella peggiore, sareste rimasti per sempre in affitto.
Prendendo un mutuo, quindi, accettate di avere subito dei soldi da investire in cambio del pagamento di interessi per un certo numero di anni.
Similmente, quando si deve sviluppare una nuova funzionalità in un sistema software (o magari costruirlo da zero), si ricorre spesso ai dei compromessi sulla qualità del codice scritto, con lo scopo di rilasciare tale funzionalità nel più breve tempo possibile.
I motivi di questa costante rincorsa possono essere disparati: si va dalla necessità di dover essere pronti a fronteggiare una nuova compliance burocratica, alla volontà di mettere una funzionalità in mano ai vostri utenti prima che lo faccia un vostro competitor oppure semplicemente perchè ogni mese ci sono stipendi e servizi da pagare, e per farlo dobbiamo mettere nelle mani dei nostri venditori un prodotto che sia sempre più vendibile e per cui sempre più clienti siano disposti a pagare.
Molte aziende del nostro settore vivono purtroppo in uno stato di perpetua fretta: si è sempre con l’acqua alla gola e per molte di queste prendere scorciatoie non è l’eccezione ma bensì la regola da seguire sempre.
In questo capitolo vi vogliamo raccontare il pericolo che si corre operando come un’azienda dove il mantra da seguire è quello di prendere tutte le scorciatoie possibili e risparmiare tempo e denaro in qualsiasi situazione, anziché costruire qualcosa con l’intento di durare nel tempo.
Ognuno di questi compromessi che scegliamo di seguire porta inevitabilmente con sé un insieme di sub-ottimalità che si manifestano nella qualità del codice o della sua documentazione. Giorno dopo giorno, queste vanno a cumularsi con quelle già precedentemente introdotte nella codebase, andando a prendere parte all’insieme di tutte queste sub-ottimalità che lentamente ed inesorabilmente cresce: questo è il debito tecnico.
Il debito tecnico non deve essere visto come un qualcosa di negativo in termini assoluti, ma bensì come uno strumento fisiologico per un’azienda che sviluppa software; tutto sta nel saper bilanciare nel modo giusto il suo utilizzo e nell’avere poi la diligenza, quando sarà il momento, di fermarsi a sanarlo. La parte difficile è proprio quella di capire quando è il momento giusto: quanto debito tecnico è accettabile? Quando è che arriva il momento di fare pausa, respirare e profondamente e scegliere di dedicare il giusto tempo a sanare un po’ di questo deficit?
Sfortunatamente per noi, non esiste una regola unica per etichettare in modo univoco che cos’è e che cosa non è debito tecnico: la sua individuazione ha un carattere decisamente soggettivo.
Per dirla tutta, da una parte è difficile misurare la quantità di debito che viene introdotta ad ogni ciclo di implementazione di nuove funzionalità; dall’altra è ancora più complicato avere visibilità circa il debito di cui siamo già in possesso.
È facile intuire come la quantità del debito tecnico che introduciamo possa dipendere in gran parte anche dalla qualità del team di sviluppo; tuttavia dobbiamo essere consapevoli che anche il miglior gruppo di ingegneri informatici dovrà ricorrere ad una certa quantità di debito tecnico nel suo sviluppo quotidiano.
Per comprendere bene l’impatto che questo può avere su di una codebase (e quindi sul business), è di fondamentale importanza fare una primissima distinzione tra debito tecnico intenzionale e debito tecnico accidentale.
Il debito tecnico intenzionale, la polvere sotto il tappeto prima o poi va tolta
Il debito tecnico intenzionale è quello “buono”, cioè quello che si contrae in maniera consapevole e allo stesso tempo prudente.
Per essere pienamente consapevoli del debito che si è deciso di contrarre, è necessario che:
– Il team abbia effettuato un’analisi sui rischi e sulle conseguenze che tale debito può avere fino a quando non verrà ripagato;
– Ci si sia fatti un’idea sul “come” questo debito debba essere rimosso;
– Come naturale conseguenza, si abbia un piano su “quando” effettuare questa rimozione.
I motivi per cui a volte è accettabile introdurre del debito tecnico consapevole sono molteplici. Per esempio quando abbiamo sviluppato in Slope la funzionalità “calendario delle prenotazioni” (planning), abbiamo scelto di implementarlo con un render lato server di una tabella HTML. Questo ha portato con sé un grande vantaggio: era la cosa più veloce che potevamo fare.
I clienti ci chiedevano un modo per visualizzare le prenotazioni all’interno di un calendario, e noi dovevamo rendere possibile questa cosa per evitare di perdere preziose quote di mercato (ossia perdere coloro che erano già clienti ma anche perdere nuove acquisizioni). Quindi abbiamo trovato una soluzione che era veloce da implementare e in linea di massima poteva essere ritenuta soddisfacente per buona parte dei clienti che ci chiedevano la funzionalità.
Abbiamo quindi rilasciato la funzionalità planning sapendo però che portava con sé una serie di problemi, alcuni visibili all’utente finale come:
– Relativa lentezza di caricamento;
– Necessità di ricaricare la pagina ogni volta che si faceva una modifica, che contribuiva a peggiorare l’usabilità;
– Limite del numero di risorse che potevano essere visualizzate a schermo.
Ma i problemi non finivano qui, dal punto di vista tecnico ingegneristico:
-Avevamo una non testabilità del codice attraverso test unitari, dovevamo ricorrere solo a test funzionali;
– Il codice della template della pagina era molto articolato, complesso da leggere e modificare: ogni intervento da parte di un ingegnere richiedeva attenzione chirurgica, perché la probabilità di -Rompere qualcosa era piuttosto elevata.
-Potremmo continuare così ancora per un po’, ma probabilmente avete già capito il concetto.
Il calendario delle prenotazioni era stato comunque implementato (in poco tempo) e potevamo finalmente annunciarlo ai nostri clienti e prospect. Giunti a questo punto, però, sapevamo tutti che questa sarebbe stata una soluzione relativamente temporanea: avremmo dovuto collezionare feedback e metriche di utilizzo per poi riprogettare il componente da zero.
Qui arriva la parte difficile, cioè quella in cui per esperienza sappiamo che molte aziende sbagliano. Anziché avere la consapevolezza che un determinato ramo di codice non può essere espanso in modo efficiente e scalabile, decidono di continuare a svilupparci sopra comunque.
“Aggiungiamo questa piccola funzionalità al planning, non è niente di chè, cosa da poco”, oppure
“Aggiungiamo quest’altra cosa che ci stanno chiedendo molti clienti, un paio di giorni di lavoro e li facciamo contenti” e così via, funzionalità sopra funzionalità, si accumula debito tecnico con tassi di interessi da usura.
Si arriva infatti ad un punto in cui si è investito talmente tanto tempo e denaro per costruire funzionalità sopra a un codice subottimale, che tornare indietro non è più un’opzione. Così i nostri cicli di sviluppo e di rilascio si rallentano, il codice diventa sempre meno comprensibile e manutenibile: ciò che ne risente è ovviamente la bontà del prodotto finale, quella che percepisce il cliente.
Un’azienda tech first non può cadere questo tranello e dopo più di un anno di glorioso servizio abbiamo deciso di riscrivere la funzionalità di planning con una nuova tecnologia che era in grado di soddisfare a pieno i requisiti di prodotto e tecnici che ci eravamo posti dopo aver misurato l’utilizzo della funzionalità precedente.
Abbiamo pensionato senza alcuna nostalgia quel codice che era stato sviluppato in poco tempo e su quale non abbiamo investito granché, e lo abbiamo sostituito con un’applicazione nuova, sviluppata senza pressioni di deadline ma soprattutto seguendo una specifica di prodotto chiara: sapevamo esattamente che cosa volevano i clienti e che cosa avremmo dovuto fare.
La riscrittura di un ramo del codice di un’applicazione è un processo abbastanza drastico; ci piaceva portarlo come esempio perché è stata una decisione che oggi, a posteriori, riteniamo ovvia ma durante la fase di scelta e decisione è stata un po’ sofferta (buttare nel cestino qualcosa che “funziona” non è mai facile da far capire a tutti i membri del team, specialmente ai meno tecnici).
Oltre alla totale riscrittura di un blocco funzionale (cosa che, ripetiamo, dovrebbe essere un’eccezione e non la regola) è ovviamente possibile, e soprattutto più gestibile, ridurre il debito tecnico anche in dosi più piccole, e cioè attraverso l’attività di refactoring continuo.
In base ai piani e alla disponibilità degli sviluppatori, questa attività di refactoring “bonificatore” può essere pianificata o appena dopo il rilascio, oppure deve essere messa in conto come prerequisito per uno sviluppo futuro che andrà ad agire sulle stesse parti di codice, o su parti limitrofe.
A questo proposito dobbiamo considerare che, in generale, la quantità di tempo e di fatica mentale necessaria per rifattorizzare codice oggetto di debito tecnico sarà tanto più alta quanto tempo si fa passare tra l’introduzione di quest’ultimo e, appunto, il refactoring.
Questo succede tipicamente per due motivi principali:
-Dopo un po’ di tempo lo sviluppatore si dimentica dei dettagli, sia tecnici che concettuali, relativi ad una determinata funzionalità e a come è stata implementata;
-La naturale tendenza di ogni codebase è quella di espandersi e complicarsi con il tempo, per cui ad ogni nuova aggiunta di codice c’è il rischio di avere un potenziale ostacolo in più da “saltare” -Quando il momento del refactoring arriverà.
Il nostro consiglio è quindi quello di cercare di non procrastinare troppo, e di allocare regolarmente tempo nei vostri planning settimanali per attività di refactoring al fine di rimuovere debito tecnico nel modo più efficiente e meno oneroso possibile.
Il debito tecnico accidentale
Ben più problematico rispetto al debito tecnico intenzionale vi è invece il debito tecnico cosiddetto accidentale, cioè quello che viene introdotto senza però avere consapevolezza che che lo si sta introducendo.
Alcuni esempi illustri di situazioni che generano debito tecnico accidentale possono essere:
carenza di persone qualificate nel team di sviluppo
alto ricambio di personale
codice in outsourcing
scarsità di documentazione nel codice esistente
processo di sviluppo non strutturato
In generale, un team di sviluppo non coeso, che quindi non lavora all’unisono verso una direzione comune, sarà più propenso ad introdurre debito tecnico. Anche la motivazione del singolo gioca un ruolo importante: se l’individuo non ha interesse a fare il bene del prodotto e dell’azienda, sceglierà sempre la strada più breve per svolgere qualunque compito.
In realtà, questa regola vale per qualsiasi dipartimento e non solo per l’ingegneria. Il problema nell’ambito ingegneristico però può manifestarsi con maggiore gravità, visto che il debito introdotto ha un peso che durerà per tutta la vita del prodotto, o almeno fino a che non verrà dedicato del tempo per rimuoverlo; altri dipartimenti hanno la “fortuna” di avere un impatto più o meno forte, ma probabilmente meno duraturo nel tempo.
Inoltre, secondo la nostra esperienza, tanto debito tecnico che nasce come intenzionale finisce inevitabilmente per diventare accidentale: questo succede spesso nelle aziende di software che non sono tech driven, oppure quelle che sono sotto costante pressione da parte del mercato o dei propri investitori per rilasciare continuamente nuove funzionalità.
Anche per questa ragione è di fondamentale importanza che il processo di decision making di un’azienda di software tenga in considerazione anche la voce dell’ingegneria. Sarà compito di questo dipartimento rendere chiara la necessità di considerare il refactoring come naturale precondizione di sviluppi ulteriori.
È spaventoso pensare come il debito tecnico diventi estremamente dannoso anche nel contesto del recruiting e retention dei talenti. Nessuno ha piacere a lavorare in un ambiente caotico e disorganizzato, allo stesso modo nemmeno su di una codebase stracolma di debito tecnico.
Questo perché il lavoro di sviluppo su una piattaforma indebitata è complesso, faticoso e molto poco gratificante: si spende buona parte del tempo ad applicare toppe a problemi che non si riescono a risolvere in maniera definitiva, le cose vanno a rilento e la frustrazione regna sovrana.
Un’altra importante considerazione da fare, è che mentre il debito finanziario, quello che un’azienda ha nei confronti di banche e investitori, è misurabile facilmente e soprattutto in maniera oggettiva.
Il debito che si ha nei confronti della tecnologia, invece, è molto più difficile da identificare e quantificare; tuttavia, il fatto che questo tipo di debito sia meno visibile e immediato non significa che possa essere ignorato e, anzi, è proprio questo il fattore che lo rende estremamente pericoloso.
Riassumendo:
L’impatto che porta con sé uno stile di sviluppo che tende ad accumulare debito tecnico è trasversale:
nel tempo impatta inesorabilmente la velocità di sviluppo che viene rallentata progressivamente;
fa perdere competitività all’azienda poiché il prodotto farà difficoltà ad essere innovato e migliorato;
causa frustrazione del personale e dei membri del team.
