Nel secondo capitolo di questa serie – DevOps per la vittoria, esploriamo i passi iniziali cruciali di un percorso di trasformazione DevOps. I primi passi in qualsiasi percorso di trasformazione sono spesso i più difficili. In DevOps, identificare i giusti passi iniziali – quelli che sono sia efficaci che realizzabili – è fondamentale per guidare un progresso significativo.
Molte organizzazioni commettono l'errore di implementare ciecamente le migliori pratiche senza considerare i propri vincoli unici, portando spesso a frustrazione e stanchezza da fallimento. È invece necessario un approccio più strategico, che identifichi i colli di bottiglia e li rimuova sistematicamente.
Come identificare i primi passi?
Un principio potente per identificare da dove iniziare deriva da una ricerca pubblicata 30 anni fa in The Goal: A Process of Ongoing Improvement di Eliyahu M. Goldratt. L'idea è semplice: identificare il collo di bottiglia principale, ovvero il fattore limitante che restringe le prestazioni del sistema. Nel nostro caso, questo è diventato chiaro quando abbiamo visto il lavoro accumularsi presso il team di sviluppo. I compiti venivano avviati ma non completati, e il backlog continuava a crescere.
Per scoprire le cause profonde, abbiamo organizzato sessioni approfondite con i team di sviluppo, i responsabili tecnici e i product manager. Queste discussioni hanno evidenziato diverse questioni chiave:
- I team stavano assumendo nuovi compiti prima di completare quelli esistenti.
- Non esistevano criteri chiari per il completamento delle attività; ciò che gli sviluppatori consideravano “completato” spesso differiva dalle aspettative degli stakeholder.
- Non c'erano misure oggettive di qualità prima di considerare il lavoro completato, il che portava a difetti e rilavorazioni.
- Frequenti riassegnazioni hanno interrotto la stabilità del team, causando inefficienze.
Formazione di team stabili
Una delle prime misure correttive è stata la ristrutturazione dei team per garantire coerenza e focalizzazione. Invece di trattare gruppi di individui come task force ad hoc, abbiamo formato team stabili e duraturi con responsabilità chiaramente definite.
L'organizzazione di alcuni team, come quelli di sviluppo, è stata relativamente semplice. Tuttavia, l'organizzazione dei team di supporto, come quelli di infrastruttura, gestione progetti, gestione prodotti e analisi aziendale, si è rivelata più complessa. Abbiamo attraversato diverse iterazioni per trovare le combinazioni giuste.
Seguendo il principio del pensiero "team-first" delineato in Team Topologies di Matthew Skelton e Manuel Pais, noi:
- Creati team piccoli e autonomi (4-7 membri) per promuovere una profonda competenza nel settore e la responsabilità.
- Eliminate le dipendenze tra team assicurando che gli individui fossero completamente assegnati a un unico team.
- Revisionate le strutture del team, in particolare per le funzioni di supporto come infrastrutture e gestione del prodotto, per ottimizzare il flusso.
Sfruttare gli strumenti per la visibilità e la gestione.
Una volta strutturati i team, siamo passati ad Azure DevOps per la gestione del backlog e del lavoro. Una piattaforma unificata ha permesso:
- Migliore visibilità del lavoro in modo che i team possano monitorare i progressi in modo trasparente.
- Definizioni standardizzate di “completato” per allineare le aspettative tra le funzioni.
- Migliore gestione del backlog, riducendo l'espansione incontrollata dell'ambito e i conflitti di prioritizzazione.
Applicazione di metriche di qualità oggettive
Per affrontare il problema delle misure di qualità poco chiare prima di contrassegnare le attività come “pronte per il test,” abbiamo integrato uno strumento di analisi statica del codice, fornendo approfondimenti obiettivi su:
- Copertura del codice
- Vulnerabilità di sicurezza
- Anomalie del codice
Abbiamo anche migliorato la nostra definizione di “Fatto” per rendere i controlli di qualità un passaggio obbligatorio prima di contrassegnare gli elementi di lavoro come completi.
Gestione del lavoro in corso (WIP)
Uno dei cambiamenti più significativi è stato il monitoraggio e la limitazione del Work in Progress (WIP). Un WIP elevato indicava colli di bottiglia, permettendoci di affrontare proattivamente le aree in cui il lavoro si bloccava.
Questo approccio sistematico, radicato in 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 realizzazioni più significative è stata che, dopo i miglioramenti iniziali, il vincolo principale al flusso si è spostato sulla progettazione del software, in particolare sulla testabilità del nostro codice. Dopo aver affrontato i colli di bottiglia iniziali migliorando la configurazione del team, definendo una "Definition of Done" 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.
Affrontare il prossimo ostacolo: il divario nei test.
Risolti i colli di bottiglia iniziali, è emerso un nuovo vincolo: i ritardi nei test. Mentre le attività di sviluppo progredivano in modo efficiente, i test sono diventati un ostacolo, impedendo rilasci più rapidi. Dopo un'indagine, abbiamo identificato le sfide chiave che rallentavano il processo:
- Dipendenza dai test manuali: La maggior parte dei test è stata eseguita manualmente, portando a cicli di feedback lenti e a un rilevamento tardivo dei difetti.
- Dipendenza dell'interfaccia utente nei test: Poiché i componenti dell'interfaccia utente venivano generalmente completati per ultimi, i test non potevano iniziare se non in fase avanzata del ciclo di sviluppo.
- Mancanza di automazione proattiva: I test automatizzati, come i test dell'interfaccia utente basati su Selenium, sono stati scritti solo dopo i test manuali, limitandone l'efficacia nella convalida precoce.
Passaggio agli approcci test-first
Per superare questo collo di bottiglia, abbiamo dato priorità ai test unitari e ai test API rispetto ai test UI. Questo cambiamento ha richiesto modifiche fondamentali nella mentalità di sviluppo e nella progettazione del software:
Miglioramenti nei test delle API
Per rendere efficiente il test delle API:
- APIs ben definite: Le APIs dovevano essere semplici, ben documentate e disponibili nelle prime fasi di sviluppo, in modo che i tester potessero creare casi di test in modo proattivo.
- Evitare il database come punto di integrazione:
- L'utilizzo di database come punto di integrazione tra i team ha creato dipendenze che hanno rallentato i test.
- La creazione di casi di test richiedeva la configurazione di database con dati complessi, il che aumentava il tempo di configurazione e limitava la capacità di testare più scenari.
- Passando all'integrazione basata su API (principalmente REST su HTTP e, più recentemente, GraphQL), abbiamo ridotto significativamente i ritardi e le complessità causati dalle dipendenze del database.
Questo cambiamento ha permesso progressi più rapidi nell'automazione dei casi di test per le APIs, dimostrando che concentrarsi su design API-first ha migliorato sia la testabilità che l'efficienza.
Il rafforzamento delle pratiche di test unitari
Il test unitario ha rappresentato una sfida maggiore:
- Progressi rapidi, poi copertura superficiale: Inizialmente, abbiamo raggiunto alti livelli di copertura del codice, ma le revisioni del codice hanno rivelato che molti test unitari 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 convalidare casi limite.
- Cambiare Mentalità: Gli sviluppatori dovevano comprendere il valore dei test unitari, non solo per migliorare la qualità del codice, ma anche per accelerare lo sviluppo individuando i problemi precocemente.
Sfide nella scrittura di codice testabile.
Il maggiore ostacolo a un efficace unit testing era la mancanza di testabilità nel codice stesso. I problemi principali includevano:
- Funzioni ampie e monolitiche.
- Componenti strettamente accoppiati.
- Scarsa separazione delle responsabilità.
- Astrazione insufficiente.
Questi problemi hanno reso difficile isolare e testare efficacemente le singole unità. Per affrontare ciò, abbiamo:
- Fornite Linee Guida: Abbiamo condiviso le migliori pratiche su:
- Selezione delle funzioni per il test unitario.
- Refactoring del codice per migliorare la testabilità.
- Progettare e utilizzare mock-up per facilitare i test.
- Concentrati sui Miglioramenti Incrementali: Gli sviluppatori sono stati incoraggiati ad apportare piccole, significative modifiche per migliorare la testabilità nel tempo.
Progressi, sfide e il percorso futuro
Per i prodotti legacy, migliorare la testabilità è rimasto un processo lento a causa dei vincoli architetturali. Tuttavia, queste azioni non erano solo volte a migliorare la codebase attuale, ma rappresentavano un investimento nel futuro:
- Gli sviluppatori hanno acquisito abitudini migliori, integrando la capacità di essere testato nei nuovi sistemi software.
- I team sono diventati più autosufficienti, riducendo la dipendenza dagli sforzi esterni di QA.
- L'organizzazione ha evitato di ripetere gli errori passati, assicurando che i prodotti futuri fossero più facili da mantenere ed evolvere.
Il messaggio chiave? DevOps non riguarda l'implementazione di strumenti o liste di controllo. Riguarda il miglioramento continuo del flusso di lavoro, un vincolo alla volta.
Nel prossimo capitolo di questa serie, esploreremo il tema della "Progettazione per la Testabilità". Approfondiremo come 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 del flusso di lavoro e infine nella testabilità, poniamo le basi per una trasformazione DevOps sostenibile.