Come ripeto spesso, essere Agili per un business è fondamentale, perchè permette di rilasciare velocemente nuovo software rispondendo alle esigenze dei clienti in modo rapido, riducendo i costi degli esperimenti e dei rilasci e permettendo quindi di raccogliere feedback e utilizzare quel feedback per fare scelte migliori.
Un passo alla volta, iterativo ed incrementale, si ottengono grandi risultati.
Ci sono tanti aspetti su cui un azienda può fare errori nel cercare di essere Agile quando nasce o mentre cresce (organizzarsi per funzioni aziendali invece che per stream di lavoro, ad esempio, oppure mantenere un approccio Waterfall che pianifica troppo upfront) ma io faccio il Technical Coach e quindi voglio rimanere nel campo dello sviluppo software e voglio fare un affermazione chiara e che non possa essere fraintesa: la nostra azienda non sarà mai davvero Agile se noi non riusciamo a garantire a livello tecnico ed implementativo l’eccellenza.
Non si può essere Agili senza eccellenza tecnica.
Cosa significa Eccellenza Tecnica: Le metriche DORA
Potremmo definire l’eccellenza tecnica come l’abilità di sviluppare e mantenere un software di alta qualità in modo veloce, efficace, e sostenibile. Raggiungere l’eccellenza tecnica nel software richiede un mix di capacità tecniche, conoscenza, ed esperienza, e l’abilità di applicarle nel lavoro quotidiano.
Nel mondo software è molto difficile essere d’accordo su cosa significhi fare “software di qualità” perchè non ci sono tanti elementi oggettivi e quindi le opinioni in merito sono molteplici; lo strumento migliore che abbiamo ad oggi per definire in modo “scientifico” cosa significhi eccellenza tecnica e cosa identifichi un team performance sono le metriche DORA, identificate dai creatori del libro Accelerate.
Il libro Accelerate, uscito nel 2018, è un libro che parla dell’impatto delle pratiche Agili sui team di sviluppo software: gli autori hanno seguito un approccio scientifico, volendo dimostrare la tesi che le pratiche Agili migliorino l’efficacia del team di sviluppo; per fare questo, la prima cosa che hanno dovuto fare è stata ideale le metriche da utilizzare per fare la valutazione: sono nate così le Software Delivery Performance Metrics, oggi note come metriche DORA (DevOps Research and Assessments).
Le 4 metriche DORA sono:
- Lead Time: il tempo tra l’ideazione di una feature e il suo arrivo agli utenti; per i team molto performanti arriva a meno di 1 ora;
- Deployment Frequency: la frequenza di rilascio in produzione; per i team molto performanti arriva ad essere on demand: rilasciano tante volte al giorno, potendo quindi rilasciare sostanzialmente in qualunque momento a richiesta, se necessario;
- Mean Time To Restore: il tempo medio di risoluzione dei problemi in produzione; per i team molto performanti arriva a meno di 1 ora;
- Change Fail Rate: la percentuale di rilasci in produzione che causa qualche tipo di problema; per i team molto performanti arriva sotto al 15%;
Il libro, consigliatissimo, descrive in modo molto importante come le pratiche Agili riescano ad impattare queste metriche, e quindi i risultati dei team di sviluppo, in modo positivo ed importante.
Al momento, questo libro e gli State of DevOps Report annuali (de facto appendici annuali del libro stesso) sono i migliori strumenti ad oggi per parlare di qualità nel software in modo più oggettivo e scientifico, e quindi sono a mio avviso il modo migliore per identificare l’eccellenza tecnica.
Perchè ci interessano le metriche DORA?
La domanda legittima che qualcuno di voi potrebbe farsi: ci interessa cosi tanto migliorare queste metriche? Ci interessa così tanto raggiungere l’eccellenza tecnica?
La mia risposta è si, assolutamente si.
Al di la degli aspetti puramente personali della professione e professionalità (personalmente questo lavoro mi piace e ci tengo sempre a fare il massimo anche solo per ambizione personale), ci sono motivi legati al successo del business che rendono questo obiettivo importante.
Facciamo un passo indietro: le aziende devono (o dovrebbero) cercare di essere Agili e rimanerlo nel tempo, perchè questo permette loro di avere successo molto più facilmente. Essere Agili significa creare MVP, esperimenti, raccogliere feedback dagli utenti per portare il nostro prodotto sulla via del successo, e per poter fare tutto questo serve il supporto giusto da parte della tecnologia.
Prendiamo ad esempio il lead time: quanto ci costa mandare una feature in produzione? Se riesco a farlo in un ora potrò fare vari esperimenti al giorno, parlare con gli utenti e capire se stiamo risolvendo i loro problemi in modo efficace o meno, in modo da aggiustare la direzione continuamente. Se una nuova feature mi costa una settimana o due di lavoro, o addirittura più settimane, la mia capacità di fare esperimenti sarà ridotta, tenderemo a fare esperimenti e feature sempre più grandi perchè un rilascio ha un costo alto, e quindi difficilmente riusciremo a rispondere ai feedback degli utenti in modo efficace; in sostanza, o indoviniamo la soluzione fin dall’inizio (cosa che non succede praticamente mai) oppure avremo problemi e difficilmente avremo successo.
Per ridurre il lead time abbiamo bisogno sicuramente anche di elementi di business e prodotto, per esempio pensare a prodotti e features in termini di Minimum Viable Product (MVP) e iterativa incrementale – ma se poi creare un MVP di un prodotto o una feature ci costa settimane o mesi tutto quello non servirà e la tecnologia anzi diventerà un freno: il business vorrebbe fare un MVP in un paio di settimane, ma anche a fronte di uno scope sostenibile il team tech non riesce a sostenere questo obiettivo.
Non si può essere Agili senza eccellenza tecnica. L’ho già detto forse?
Le pratiche Agili e i loro benefici
Per concludere il mio discorso, parliamo delle pratiche: in questa sezione voglio lasciarvi la carrellata delle principali pratiche Agili, ognuna accoppiata con i propri benefici principali.
Mi concentrerò su quelle che possono essere utili e di beneficio in qualunque contesto e situazione, lasciando stare pratiche e metodologie che richiedono un contesto specifico: per esempio, non citerò Domain-Driven Design ed Event Sourcing, essendo pratiche costose che si sposano bene quindi con contesti con alta complessità che ne giustifichi il costo.
Nota: queste pratiche formano un circolo vizioso positivo; ognuna di esse presa singolarmente porta benefici, ma insieme si potenziano in modo esponenziale.
Pair Programming
Il Pair Programming è una pratica in cui invece di dividersi i task tra singoli membri del team, viene diviso tra coppie: ogni coppia di programmatori lavora al codice insieme.
Questa pratica, vittima di tanti pregiudizi, viene spesso vista come insostenibile: due persone che fanno il lavoro di una? Sembra assurdo. Lo sarebbe davvero, se solo il lavoro principale dello sviluppatore fosse scrivere codice – ma non è così!
Nello sviluppo software il coding è solo una parte molto piccola del lavoro: il resto è esplorazione del problema, raccolta e reazione ai feedback, ecc. La parte di scrittura di codice è molto ridotta in confronto a queste, quindi l’impatto di avere due persone farlo insieme è minimo sul coding – ma allo stesso tempo è un impatto enorme sui vari elementi che possono causare unplanned work: le PR, se ancora presenti nel flusso del team, saranno più snelle, i bug saranno ridotti cosi come le chance di aver capito male il problema (defects), perchè lavorando insieme le due persone si sono aiutate a vicenda ed evitate errori l’uno con l’altro. Inoltre in questo modo il Work In Process (WIP) si riduce in modo naturale portando benefici al throughput del team.
La riduzione di unplanned work ripaga totalmente l’investimento.
Ensemble Programming
L’Ensemble Programming (aka Mob Programming) è una pratica in cui il team lavora insieme allo sviluppo di un nuovo pezzo di codice, anche (e sopratutto) durante il coding. Potremmo anche definirlo come il Pair Programming allargato all’intero team (il che in un team cross-funzionale, idealmente, include anche le figure “non dev” come UX, PM, QA, Platform, etc).
I benefici sono gli stessi del Pair Programming, ma portati all’estremo: il WIP è limitato a 1 per l’intero team, massimizzando il throughput; dato che l’intero team è coinvolto, le review non sono più necessarie, ed i defect/bugs saranno vicini allo zero.
Anche qui, dobbiamo superare alcuni pregiudizi: come detto il tempo di coding è minimo nel lavoro di un team, quindi che l’intero team lavori insieme ha un impatto minimo rispetto al ritorno in riduzione di unplanned work, che è massimale in questo caso. Molti team hanno già compreso quanto sia potente lavorare in team in momenti di analisi, refinement, raccolta e analisi dei feedback, ideazione di soluzioni, etc – si tratta solo di aggiungere anche il coding alle pratiche svolte a livello di team.
Infine, in molti hanno paura del fatto che questo tolga tanta libertà ai membri del team, ma non è cosi: le buone pratiche di Ensemble Programming suggeriscono switch molto frequenti alla tastiera, ascolto attivo ed interattività dei membri non alla tastiera, frequenti pause di team ma anche libertà totale per pause personali aggiuntive se necessarie – se rispettate, queste pratiche portano ad un team in cui davvero nessuno deve preoccuparsi di un assenza di ore o giorni, perchè il resto del team continuerà a lavorare allo stesso modo sostanzialmente senza impatti.
Test-Driven Development (TDD)
TDD è un altra pratica che tipicamente causa discussioni accese tra i Developers, ma di cui è stata ormai dimostrata l’efficacia. Anche in questo caso, pregiudizi e malintesi la fanno da padrone.
Partiamo dalla definizione tipica: TDD è la pratica di scrivere i test prima dell’implementazione. Questa definizione è sbagliata, portemmo definirla definizione di “Test-First Programming”, ma di certo non di TDD, perchè è sicuramente vero che in TDD scriviamo prima i test, ma manca una sfumatura fondamentale del TDD: i baby steps.
Procedere a piccoli passi è un elemento fondamentale del TDD, come espresso in particolare dalle 3 leggi di TDD di Kent Beck, che parafrasate ci dicono:
- scrivi un test finchè il sistema non fallisce: questo significa che uno step non è necessariamente il test intero, ma un pezzo del test – appena fallisce, fermati, implementa, e poi aggiungi un altro pezzo al test
- scrivi codice di produzione solo per passare il test che fallisce: mentre un test è rosso, il tuo unico obiettivo è renderlo verde il prima possibile
- non aggiungere codice di produzione quando il test è passato: appena il test è verde, fermati; puoi fare refactoring, ma non aggiungere altri comportamenti
L’idea dei baby step è fondamentale che uno step in TDD non è un test interno ma ogni singolo pezzo di quel test che aggiungo e che lo rendono rosso: se ad esempio ho bisogno di chiamare un metodo subscribe
che non esiste, appena lo chiamo il test sarà rosso; TDD ci chiede di fermarci, andare nel codice di produzione, implementarlo rendendo il test verde, e poi proseguire col test – e così via fino ad aver testato il comportamento che volevamo.
Questo è ciò che rende TDD così potente per esplorare problemi: meno la soluzione del problema, e quindi l’implementazione, ci è chiara, più vorremo affidarci a questo flusso di TDD in modo rigido per farci guidare – al contrario, se conosciamo già la soluzione, saramo più laschi e faremo step più grandi perchè abbiamo maggiore confidenza.
Una volta imparato TDD, non si torna indietro.
Clean Code
Uso il termine Clean Code per riferirmi a tutte quelle pratiche di buon design che rendono un codice migliore in termini di leggibilità e manutenibilità.
Il nostro obiettivo è rimanere veloci, come abbiamo visto, e per farlo deve essere facile e veloce leggere e modificare il nostro codice: nessuna pratica o metodologia ci verrà in aiuto se non ci impegneremo nel design.
Rimanendo sul design del codice, per esempio, dobbiamo evitare i malintesi tipici della Programmazione ad Oggetti: usare getters e setters per esempio è una pessima idea. Rimaniamo fedeli ai principi di OOP e cerchiamo di costruire un “sistema di oggetti che comunicano tra loro tramite messaggi“, esponendo solo comportamenti (messaggi) dalle nostre classi, mai dati interni.
Affidiamoci alle regole di Object Calisthenics in primis, impariamo a riconoscere i Code Smell e ad evitarli (e Object Calisthenics fa proprio questo) e comprendiamo i principi SOLID per assicurarci che il nostro sistema cresca nel tempo mantenendo facilità di cambiamento.
Refactoring
Il refactoring è l’ennesimo principio di programmazione frainteso, malinteso ed usato male – lo so, sono ripetitivo, ma è la verità, e questi articoli nascono proprio dalla voglia di togliere un pò di nebbia da questi concetti.
La definizione migliore di Refactoring ce la dà Martin Fowler nel suo libro sul tema: ristrutturare un software applicando una serie di modifiche senza cambiarne il suo comportamento visibile. Tipicamente, vogliamo fare refactoring per migliorare il design del codice e la sua leggibilità, rendendo più facile risolvere bug ed aggiungere features: dall’esterno, quel pezzo di codice riceve gli stessi input e restituisce gli stessi output e modifiche al sistema, ma internamente lo sta facendo in modo differente.
Uno dei più grandi malintesi sul refactoring riguarda il quando, e quanto spesso, farlo: il Refactoring viene spesso considerato come un lavoro a parte, a volte addirittura raggruppato tutto insieme in periodi molto lunghi; in questo modo, diventa molto difficile giustificarlo: significa investire alcune ore (o anche giorni o settimane nei casi peggiori) in modifiche al codice che non portano nulla a livello di business. Certo, noi sappiamo che a livello tecnico ci serve a tenere in piedi il sistema, ma non è sostenibile pensare di farlo fermando gli sviluppi.
Dobbiamo considerare invece il refactoring una parte del lavoro quotidiano: ogni volta che devo fare modifiche al codice devo chiedermi se quella modifica è semplice; se lo è, procedo – ma se non lo è, prima faccio refactoring e la rendo tale, e poi la implemento.
Fare refactoring in modo continuo, come parte integrante del flusso di lavoro, è l’unico modo per tenere la nostra codebase pulita in modo sostenibile. E per farlo abbiamo bisogno di un solo prerequisito: una suite di test veloce (pochi minuti) ed affidabile, che ci permetta di fare refactoring assicurandoci che il comportamento non cambi. Idealmente, anche durante step intermedi di refactoring, i test dovrebbero rimanere SEMPRE verdi, senza rompersi mai.
“Make the change easy, then make the easy change”.
Continuous Delivery
Altra pratica, altri fraintendimenti: in molti credono che fare CD significhi semplicemente avere una pipeline automatica, ma non è così; “fare CD” significa applicare una serie di pratiche, tra le quali fa sicuramente parte anche la pipeline automatica ma è solo una fra tante.
Alcuni elementi che fanno parte di CD e che quasi mai sono compresi correttamente sono, ad esempio:
- Continuous Integration: il codice arriva su master almeno una volta al giorno, con test automatici che ci garantiscono che tutto funzioni prima e dopo l’integrazione su master
- La pipeline automatica è l’unico modo per rilasciare qualunque ambiente
- La pipeline automatica decide se una modifica può essere rilasciata ed il suo verdetto è indiscutibile (il che ad esempio implica anche il BDD, di cui parleremo dopo, o comunque una forma automatica di test degli acceptance criteria)
- … ecc
Implementare correttamente la CD sostanzialmente ci aiuta a ridurre l’overhead tipicamente causato da sistemi instabili e rilasci rischiosi perchè è tutto automatizzato e gestito in modo ingegneristico tramite software, rendendolo robusto e non incline ad errori umani.
La migliore risorsa online sul tema è sicuramente minimumcd.org.
Trunk-Based Development (TBD)
Concentrandosi maggiormente sulla Continuous Integration (CI), è importante fare un focus sul Trunk-Based Development (TBD). Come nel caso della CD, “fare CI” significa implementare una serie di comportamenti che insieme compongono quella che viene definita CI, e tra questi c’e anche il Trunk-Based Development.
Possiamo definire il TBD come “una metodologia nella quale le modifiche al codice vengono integrate direttamente sul branch principale, senza la creazione di altri branch di lavoro nel mezzo (Develop, Test, Feature Branch, etc.)“. In pratica, TBD significa fare commit direttamente su master, o in alternativa creare un branch da master ma mergiarlo entro un giorno – e fare CI significa TBD + le automazioni di pipeline necessarie attorno per verificare che tutto funzioni come ci aspettiamo, sia prima che dopo l’integrazione del codice sul branch principale.
Il Trunk-Based Development non è cosi diffuso principalmente perchè come sviluppatori siamo sempre stati abituati a due principi che il TBD ribalta:
- feature branches, o comunque lavoro in branch molto lunghi – una pratica che nasce ed ha senso nel contesto Open Source ma molto molto molto meno in un contesto di business, dove la fiducia tra i membri del team dovrebbe essere molto più alta
- rilasciare features complete – da sempre ci insegnano che il rilascio è lo step finale di un processo molto lungo, ma se è sicuramente vero che prima di rilasciare qualcosa devo averlo sviluppato, non significa che debba per forza essere una feature molto grande; dobbiamo abituarci a rilasciare molto di frequente, il che significa rilasciare lavoro non completo, ed imparare quindi i pattern per nascondere quel lavoro fino al completamento.
La migliore risorsa online sul tema è sicuramente trunkbaseddevelopment.com.
Behaviour-Driven Development (BDD)
L’ultima pratica di cui voglio parlare è il Behaviour-Driven Development (BDD), che ha molta affinità con l’ Acceptance Test-Driven Development (ATDD).
In questo approccio outside-in allo sviluppo vogliamo formalizzare quali sono gli acceptance criteria della nostra user story, e qual’è la definition of done – non tramite documentazione fine a se stessa, ma tramite test automatici che ci permettono quindi di assicurarci che quel comportamento sia stato raggiunto.
Gli Acceptance Test possono (devono!) essere automatizzati, e per essere efficaci devono avere alcune caratteristiche chiare:
- essere leggibili anche da persone non tecniche
- descrivere una feature in modo completo
- essere veloci (chiamano direttamente l’application layer, tipicamente)
Grazie al BDD potremo quindi garantire che i requisiti siano stati rispettati a pieno.
Conclusioni
Spero che questa carrellata possa essere stata utile a chiarire alcuni dubbi su queste pratiche, ed anche i motivi per cui le considero così importanti. So di ripetermi, ma fatico a trovare contesti in cui queste pratiche non possano portare beneficio e migliorare l’efficacia ed i risultati di un team.
Il mio consiglio come sempre è di fare delle prove: fate dei Kata e dei side projects e cercate di guadagnare abbastanza conoscenza di queste pratiche per poi iniziare ad applicare in applicazioni di business reali. Sono certo che i benefici saranno evidenti sin dai primi cicli di sviluppo, e non potrete più farne a meno.
Se questo articolo ti è piaciuto, sappi che è parte di una serie mensile a tema Agile, il che significa che puoi recuperare i precedenti e che ne arriveranno altri nei prossimi mesi! In più, se ti piace il mio stile, dai un occhiata a Learn Agile Practices, il mio ecosistema di contenuti online nei quali parlo di pratiche e metodologie Agile come TDD, CI, CD e molto altro a tema programmazione:
- Learn Agile Practices – una newsletter settimanale (in inglese) – iscriviti qui ➡️ https://learnagilepractices.substack.com/about
- Video Podcast, principalmente in italiano – scopri tutti i link per le varie piattaforme di podcast ➡️ Spreaker o Spotify e iscriviti al canale Youtube ➡️ qui