Nel secondo capitolo di questa serie, intitolato DevOps for the Win, esaminiamo i primi passi fondamentali di un percorso di trasformazione DevOps. L'inizio di qualsiasi percorso di trasformazione è spesso la fase più difficile. Nell'ambito DevOps, individuare i primi passi giusti, che siano al contempo efficaci e realistici, è fondamentale per ottenere progressi concreti.
Molte organizzazioni commettono l'errore di applicare ciecamente le best practice senza considerare i propri vincoli specifici, causando spesso frustrazione e insuccessi. È invece necessario un approccio più strategico, che individui i colli di bottiglia e li rimuova in modo sistematico.
Come individuare i primi passi?
Un principio efficace per capire da dove cominciare deriva da una ricerca pubblicata trent'anni fa in The Goal: A Process of Ongoing Improvement di Eliyahu M. Goldratt. L'idea è semplice: individuare il collo di bottiglia principale, ovvero il fattore limitante che frena le prestazioni del sistema. Nel nostro caso, la situazione è diventata evidente quando abbiamo visto il lavoro accumularsi presso il team di sviluppo. Le attività venivano avviate ma non completate, e il lavoro arretrato continuava a crescere.
Per individuarne le cause profonde, abbiamo organizzato sessioni di approfondimento con i team di sviluppo, i responsabili tecnici e i product manager. Da questi confronti sono emersi diversi problemi chiave:
- I team assumevano nuovi incarichi prima di aver completato quelli esistenti.
- Non esistevano criteri chiari per il completamento delle attività: ciò che gli sviluppatori consideravano "completato" spesso non coincideva con le aspettative degli stakeholder.
- Non vi erano misurazioni oggettive della qualità prima di contrassegnare un lavoro come completato, il che ha causato difetti e la necessità di rilavorazioni.
- I frequenti cambi di assegnazione compromettevano la stabilità del team, causando inefficienze.
Creazione di team stabili
Una delle prime misure correttive è stata la riorganizzazione dei team per garantire coerenza e concentrazione. Invece di considerare i gruppi di persone come task force temporanee, abbiamo creato team stabili e duraturi con responsabilità chiaramente definite.
La creazione di alcuni team, come quelli di sviluppo, è stata relativamente semplice. Tuttavia, organizzare i team di supporto (come infrastruttura, gestione dei progetti, gestione del prodotto e analisi aziendale) si è rivelato più complesso. Abbiamo effettuato diversi tentativi prima di trovare le combinazioni giuste.
Seguendo il principio prima il team descritto in Team Topologies di Matthew Skelton e Manuel Pais, abbiamo:
- Creato team piccoli e autonomi (da 4 a 7 membri) per promuovere una profonda competenza di settore e un maggiore senso di responsabilità.
- Eliminato le dipendenze tra i team assicurandoci che ogni persona fosse assegnata a tempo pieno a un unico team.
- Ottimizzato le strutture dei team, in particolare per le funzioni di supporto come l'infrastruttura e la gestione del prodotto, per migliorare il flusso di lavoro.
Sfruttamento di strumenti per la visibilità e la gestione
Una volta strutturati i team, siamo passati a Azure DevOps per la gestione del backlog e delle attività. Una piattaforma unificata ha permesso di ottenere:
- Una migliore visibilità del lavoro, consentendo ai team di monitorare i progressi in modo trasparente.
- Definizioni standardizzate di "completato" per allineare le aspettative tra le diverse funzioni.
- Una gestione migliorata del backlog, riducendo l'aumento incontrollato del carico di lavoro e i conflitti di priorità.
Applicazione di metriche di qualità oggettive
Per risolvere il problema della mancanza di criteri di qualità chiari prima di contrassegnare le attività come "pronte per i test", abbiamo integrato uno strumento di analisi del codice statico, che fornisce dati oggettivi su:
- Copertura del codice
- Vulnerabilità di sicurezza
- Anomalie del codice (code smells)
Abbiamo inoltre migliorato la nostra definizione di "Completato" per rendere i controlli di qualità un passaggio obbligatorio prima di contrassegnare gli elementi di lavoro come terminati.
Gestione del lavoro in corso (WIP)
Uno dei cambiamenti di maggiore impatto è stato il monitoraggio e la limitazione del lavoro in corso (WIP). Un WIP elevato indicava colli di bottiglia, consentendoci di intervenire in modo proattivo sulle aree in cui il lavoro si bloccava.
Questo approccio sistematico, basato su The Goal e Team Topologies, ha gettato le basi per il miglioramento continuo, riducendo le dipendenze e migliorando la responsabilità.
Nel nostro percorso di trasformazione DevOps, una delle prese d'atto più significative è stata che, dopo i miglioramenti iniziali, il principale vincolo al flusso si è spostato sulla progettazione del software, in particolare sulla testabilità del nostro codice. Dopo aver risolto i colli di bottiglia iniziali migliorando la configurazione del team, definendo una "definizione di completamento" e implementando l'analisi del codice, abbiamo notato che il lavoro si accumulava durante la fase di test. Il codice sviluppato dai team Scrum era spesso in attesa di essere testato dai membri QA all'interno degli stessi team Scrum.
Risoluzione del collo di bottiglia successivo: il divario nei test
Risolti i problemi iniziali, è emerso un nuovo ostacolo: i ritardi nei test. Mentre le attività di sviluppo procedevano in modo efficiente, i test sono diventati un blocco, impedendo rilasci più rapidi. A seguito di un'analisi, abbiamo individuato le principali criticità che rallentavano il processo:
- Dipendenza dai test manuali: la maggior parte dei test veniva eseguita manualmente, portando a cicli di feedback lenti e a un rilevamento tardivo dei difetti.
- Dipendenza dall'interfaccia utente (UI) nei test: poiché i componenti dell'interfaccia venivano solitamente completati per ultimi, i test non potevano iniziare fino alle fasi finali del ciclo di sviluppo.
- Mancanza di automazione proattiva: i test automatizzati, come i test UI basati su Selenium, venivano scritti solo dopo i test manuali, limitandone l'efficacia nella convalida iniziale.
Passaggio ad approcci basati sui test
Per superare questo collo di bottiglia, abbiamo dato priorità a unit test e test API rispetto ai test UI. Il cambiamento ha richiesto modifiche fondamentali nella mentalità di sviluppo e nella progettazione del software:
Miglioramenti nei test delle API
Per rendere efficienti i test delle API:
- API ben definite: le API dovevano essere semplici, ben documentate e disponibili all'inizio della fase di sviluppo in modo che i tester potessero creare casi di test in modo proattivo.
- Evitare il database come punto di integrazione:
- L'uso dei database come punto di integrazione tra i team ha creato dipendenze che hanno rallentato i test.
- La creazione dei casi di test richiedeva la configurazione di database con dati complessi, aumentando i tempi di preparazione e limitando la capacità di testare più scenari.
- Passando all'integrazione basata su API (principalmente REST su HTTP e, più recentemente, GraphQL), abbiamo ridotto in modo significativo i ritardi e le complessità causati dalle dipendenze del database.
Questo cambiamento ha consentito progressi più rapidi nell'automazione dei casi di test per le API, dimostrando che concentrarsi su design basati sulle API ha migliorato sia la testabilità che l'efficienza.
Rafforzare le pratiche di unit testing
Gli unit test hanno presentato una sfida maggiore:
- Progressi rapidi e poi copertura di facciata: inizialmente abbiamo raggiunto alti livelli di copertura del codice, ma le revisioni del codice hanno rivelato che molti unit test erano superficiali, scritti solo per raggiungere gli obiettivi di copertura. Questi test non sono riusciti a verificare funzionalità significative, a coprire scenari di errore o a validare casi limite.
- Cambiamento di mentalità: gli sviluppatori dovevano comprendere il valore degli unit test, non solo per migliorare la qualità del codice ma anche per accelerare lo sviluppo individuando i problemi in anticipo.
Le sfide nella scrittura di codice testabile
Il principale ostacolo a un unit testing efficace era la mancanza di testabilità nel codice stesso. I problemi chiave includevano:
- Funzioni grandi e monolitiche.
- Componenti fortemente accoppiati.
- Scarsa separazione delle responsabilità.
- Astrazione insufficiente.
Questi problemi rendevano difficile isolare e testare efficacemente le singole unità. Per risolvere questo problema, abbiamo:
- Linee guida fornite: Abbiamo condiviso le migliori pratiche su:
- Selezionato le funzioni per l'unit testing.
- Il refactoring del codice per migliorarne la testabilità.
- Progettato e utilizzato mock per facilitare i test.
- Focus sui miglioramenti incrementali: Gli sviluppatori sono stati incoraggiati a fare piccoli e significativi cambiamenti per migliorare la testabilità nel tempo.
Progressi, sfide e prospettive future
Per i prodotti esistenti, il miglioramento della testabilità è rimasto un processo lento a causa di vincoli architetturali. Tuttavia, queste azioni non miravano solo a migliorare il codice attuale, ma rappresentavano un investimento per il futuro:
- Gli sviluppatori hanno acquisito abitudini migliori, integrando la testabilità nelle nuove basi di codice.
- I team sono diventati più autonomi, riducendo la dipendenza dalle attività di QA esterne.
- L'organizzazione ha evitato di ripetere gli errori del passato, assicurandosi che i prodotti futuri fossero più facili da gestire ed evolvere.
Il concetto chiave? DevOps non significa implementare strumenti o checklist, ma migliorare continuamente il flusso di lavoro, affrontando un vincolo alla volta.
Nel prossimo capitolo di questa serie approfondiremo la "Progettazione orientata alla testabilità". Esamineremo più da vicino in che modo il miglioramento della progettazione del software possa eliminare i vincoli di test, consentendo cicli di feedback più rapidi e una maggiore qualità del software (prima nelle strutture dei team, poi nella gestione dei flussi di lavoro e infine nella testabilità), ponendo così le basi per una trasformazione DevOps sostenibile.
