Il cliente chiede: "Se aggiungere questa funzionalità in una nuova applicazione richiederebbe una settimana, perché qui ce ne vogliono tre?"
È una domanda molto buona.
E spesso la risposta non è: "perché i programmatori lavorano più lentamente".
Il problema può trovarsi molto più in profondità - nell'architettura del sistema, nelle sue dipendenze, nel modo in cui vengono conservati i dati, nella mancanza di test, nelle decisioni storiche e nei cambiamenti successivi aggiunti nel corso degli anni.
Proprio per questo il costo dello sviluppo software non è fisso.
La stessa funzionalità può costare importi completamente diversi in due sistemi differenti.
Il codice non viene valutato solo in base al numero di funzionalità
A prima vista un compito può sembrare banale.
"Aggiungiamo la possibilità di esportare i dati in Excel."
Oppure: "Aggiungiamo un nuovo ruolo utente."
Oppure: "Colleghiamo il sistema al nostro CRM."
Il problema è che una funzionalità non esiste mai completamente isolata dal resto del sistema.
La nuova funzionalità può richiedere modifiche in:
- database,
- API,
- backend,
- frontend,
- sistema dei permessi,
- logging,
- reporting,
- integrazioni,
- test,
- meccanismi di cache,
- documentazione,
- processo di rilascio.
Più il sistema è interconnesso, più elementi bisogna analizzare prima di apportare una modifica.
Il costo maggiore può nascere prima di scrivere la prima riga di codice
In un sistema maturo lo sviluppatore non dovrebbe semplicemente iniziare a scrivere.
Per prima cosa bisogna rispondere:
- Dove dovrebbe essere aggiunta questa funzionalità?
- Con quali moduli comunicherà?
- Quali dati utilizza?
- I meccanismi di autorizzazione esistenti la coprono?
- La modifica influenzerà altri processi?
- Quali test vanno aggiornati?
- L'architettura attuale consente davvero di farlo correttamente?
Tutto questo fa parte del costo di realizzazione della funzionalità.
Per questo in un sistema vecchio una parte significativa del lavoro può consistere non nella programmazione stessa, ma nel riconoscere le dipendenze e i limiti della soluzione esistente.
Il technical debt funziona come gli interessi
Un buon modo di pensare al technical debt è proprio il costo dei cambiamenti successivi.
Se una certa soluzione è stata realizzata in fretta, allora può essere del tutto giustificata.
Il problema nasce quando una soluzione temporanea diventa un elemento permanente del sistema.
Nasce un'altra funzionalità.
Poi un'altra.
Compare un'eccezione.
Poi un'altra eccezione.
A questo si aggiungono integrazione, workaround, processo manuale e regola supplementare.
Dopo alcuni anni nessuno ricorda più perché il sistema funzioni proprio in quel modo.
Ma ogni modifica successiva deve tenere conto di tutte quelle decisioni storiche.
Martin Fowler descrive il technical debt come lo sforzo aggiuntivo sostenuto durante le modifiche del sistema a causa di problemi nella sua qualità interna.
Si può quindi dire: il technical debt non deve per forza bloccare subito lo sviluppo. All'inizio fa solo sì che ogni cambiamento successivo diventi più costoso.
Primo segnale: "già che ci siamo, bisogna correggere altre cinque cose"
È uno dei sintomi più caratteristici.
Il cliente ordina una sola funzionalità.
Durante l'analisi si scopre che, per implementarla, bisogna:
- correggere la struttura della tabella,
- cambiare il metodo di autorizzazione,
- aggiornare la libreria,
- riparare la vecchia API,
- riscrivere una parte del frontend.
All'improvviso una piccola funzionalità smette di essere una piccola funzionalità. Non perché il requisito sia complesso. Ma perché il sistema non ha più adeguati confini architetturali.
Secondo segnale: una modifica richiede il test dell'intero sistema
Se una piccola modifica richiede un regression test manuale completo, l'organizzazione sta pagando per la mancanza di automazione.
Con la crescita del sistema aumenta anche il numero di possibili combinazioni.
Senza un set adeguato di test diventa sempre più difficile avere la certezza che la nuova funzionalità non abbia danneggiato quella vecchia.
Questo a sua volta genera cautela.
I rilasci sono meno frequenti.
Le modifiche sono più grandi.
Il rischio aumenta.
E rilasci più grandi sono più difficili da diagnosticare in caso di problemi.
Si crea un circolo vizioso.
Terzo segnale: "meglio non toccare questo modulo"
Questa frase dovrebbe accendere una spia d'allarme.
Se un modulo specifico è diventato un'area che il team evita perché il suo comportamento è imprevedibile, il sistema presenta un serio problema di manutenzione.
Ancora peggio se il suo funzionamento lo conosce solo una persona. In quel caso l'azienda non ha solo technical debt. Ha anche knowledge risk.
L'uscita di un dipendente può significare la perdita della conoscenza necessaria per sviluppare il sistema in sicurezza.
Quarto segnale: ogni funzionalità richiede eccezioni
Un sistema ben progettato dovrebbe avere regole prevedibili.
Se ogni nuova funzionalità richiede l'aggiunta di un'eccezione speciale, di una condizione aggiuntiva o di un percorso individuale, l'architettura probabilmente sta iniziando a limitare lo sviluppo.
Questo spesso porta a un codice che non si riesce più a prevedere facilmente.
E la mancanza di prevedibilità comporta un costo maggiore di analisi, test e manutenzione.
Bisogna riscrivere tutto?
No.
Ed è qui che arriviamo a una distinzione molto importante.
Il technical debt non implica automaticamente la necessità di un rewrite.
Le possibili soluzioni includono:
Refactoring
Cioè il miglioramento della struttura del codice esistente senza cambiarne il comportamento di business.
È una buona direzione quando il sistema ha ancora un'architettura sensata, ma alcune parti sono difficili da mantenere.
Modernizzazione di componenti selezionati
Non è necessario sostituire l'intera applicazione.
Si può iniziare dal modulo, dall'integrazione o dallo strato più problematico.
Migrazione graduale
I nuovi elementi possono funzionare accanto al vecchio sistema e le aree successive vengono trasferite progressivamente.
Questo approccio permette di limitare il rischio di una migrazione una tantum. Nella letteratura sulla modernizzazione del legacy si utilizza spesso proprio l'estrazione graduale delle funzionalità e la sostituzione delle parti successive del sistema.
Rewrite
La costruzione di un nuovo sistema ha senso quando l'architettura attuale è talmente limitante che ulteriori modernizzazioni non portano un ritorno giustificato.
Ma il rewrite dovrebbe essere una decisione basata sull'analisi, non una reazione alla frustrazione del team.
Quando non conviene ancora investire nella modernizzazione?
Il debito tecnico di per sé non è un motivo per fermare lo sviluppo. Ogni sistema ha un certo livello di debito tecnico. A volte ripagarlo non ha senso economico.
Se l'applicazione:
- funziona in modo stabile,
- è sicura,
- ha un numero limitato di modifiche,
- gestisce un processo che non si svilupperà in modo significativo,
- non genera problemi operativi,
può essere ragionevole lasciarla nello stato attuale.
Non si tratta di far sì che ogni sistema sia tecnologicamente perfetto.
Si tratta di fare in modo che il livello del debito sia una decisione consapevole.
Quando il costo del debito diventa un problema di business?
Quando inizia a influenzare i risultati dell'azienda.
Per esempio:
Una nuova funzionalità doveva essere lanciata sul mercato in un mese, ma ne richiede tre.
L'integrazione con un nuovo partner si prolunga, perché l'API del vecchio sistema non consente di gestire facilmente i nuovi dati.
Una persona chiave del team deve partecipare ogni volta ai lavori, perché solo lei conosce il vecchio modulo.
Ogni rilascio importante richiede ore di test di regressione.
Il concorrente introduce più velocemente nuove funzionalità, perché la sua piattaforma consente di sperimentare più rapidamente.
A questo punto il technical debt smette di essere un problema del reparto IT.
Diventa un problema di business.
Come misurare se la situazione sta peggiorando?
Non è necessario creare un sistema KPI complicato.
Vale la pena osservare alcuni semplici indicatori:
Lead time - quanto tempo passa dall'inizio del lavoro su una modifica al suo rilascio.
Frequenza dei rilasci - con quale frequenza il team può consegnare in modo sicuro le modifiche.
Change failure rate - con quale frequenza i rilasci causano problemi.
Tempo di ripristino - quanto velocemente si può tornare a un funzionamento stabile dopo un guasto.
Tempo di realizzazione di una funzionalità - se attività simili richiedono sempre più impegno.
A ciò vale la pena aggiungere l'analisi del numero di operazioni manuali, della copertura dei test, dell'aggiornamento delle dipendenze e del tempo necessario per inserire un nuovo sviluppatore nel progetto.
Questi dati permettono di vedere se il problema è davvero tecnico, oppure se deriva dal processo, dai requisiti o dal modo in cui è organizzato il lavoro.
La soluzione peggiore è "un'altra rapida correzione"
Se il team sa che l'architettura richiede cambiamenti, ma ogni volta rimanda il tema, il sistema può entrare in una spirale.
"Facciamo ora un workaround."
"La refactoring la faremo più tardi."
"Per ora basta."
"Al prossimo rilascio."
Il problema è che il rilascio successivo porta con sé ulteriori requisiti.
E ogni workaround successivo aumenta il costo della prossima modifica.
Per questo la decisione di ripagare il technical debt dovrebbe far parte della strategia di sviluppo del prodotto, e non essere una reazione casuale a una crisi.
Una buona applicazione non è quella che non invecchia mai
Ogni sistema cambierà.
Le tecnologie cambieranno.
I clienti avranno nuove esigenze.
Ci saranno nuove integrazioni.
Cambierà il modo di lavorare dell'azienda.
Perciò l'obiettivo non dovrebbe essere creare un'applicazione che non debba mai essere modernizzata. L'obiettivo dovrebbe essere creare un'architettura in cui la modernizzazione sia possibile senza fermare il business. È una differenza enorme.
Perché il miglior sistema non è quello che appare più moderno nel giorno del lancio. È quello che, anche dopo alcuni anni, consente all'azienda di reagire rapidamente ai cambiamenti.
E se ogni nuova funzionalità costa sempre di più, non significa sempre che la funzionalità sia difficile.
Forse è diventato difficile il sistema stesso.
