Software Development & Testing

eXtreme Programming (XP): le 4 pratiche del ciclo di feedback interno

Pair Programming, Test-Driven Development, Refactoring e Simple Design: vediamo in dettaglio le prime 4 pratiche di eXtreme Programming


Secondo articolo della serie di Nicola Moretto, developer, Agile coach e founder di QMates, sulle pratiche dell’eXtreme Programming, ospitata dal blog di UNGUESS.

Come abbiamo visto nel primo articolo di questa serie, in eXtreme Programming ci sono quattro pratiche che si concentrano sul ciclo di feedback interno, ovvero quello più breve. Le pratiche del ciclo interno costituiscono il nucleo del processo e sono Pair Programming, Test Driven Development (TDD), Refactoring e Simple Design.

 

Il Pair Programming: cos'è e come funziona

Il Pair Programming (Programmazione in Coppia) storicamente è stata la prima pratica XP creata dai suoi fondatori. Viene vista come la meta-pratica (ovvero la pratica da cui tutto inizia); l'eXtreme Programming si concentra molto sull'aspetto socio-collaborativo e il fatto che il Pair Programming sia la prima pratica è un forte segnale dell'importanza di questo elemento.

unguess_prompter_10331_pixel_art_two_developers_work_together_i_53ed2e5b-5802-4df0-97b5-2758cc2a9a1d

Pair Programming, la definizione

Il Pair Programming consiste in una conversazione costante e co-creativa tra due sviluppatori che lavorano su un singolo PC e, quindi, su una singola funzionalità. Questo approccio va nella direzione di intendere come unità minima di lavoro la coppia di sviluppatori: essi collaborano, discutono e creano soluzioni ai problemi che si presentano.

All'interno del Pair Programming ci sono due ruoli derivanti dalla metafora del rally: il driver e il navigator. Il driver è colui che ha la tastiera, scrive attivamente il codice; il navigator è un partner di discussione, il suo compito è quello di stimolare la ricerca di soluzioni nuove e sempre migliori, generalmente con una lista di task che evolve nel tempo. Questa pratica richiede una stretta collaborazione e va oltre il mero sviluppo di software, estendendosi facilmente anche ad altre alle attività quotidiane, come call con il cliente oppure interview per un potenziale nuovo membro del team.

Il ruolo del driver, chi scrive il codice, è distinto da quello del navigator, il quale gestisce una to-do list delle attività emerse durante la sessione di pair programming. Il navigator mantiene attiva la conversazione, assicurandosi che il lavoro stia seguendo la giusta direzione e verificando il design emergente.

Nella modalità più pura di pairing questi ruoli sono fluidi e variano molto spesso durante il giorno in base all'esigenza.

 

Come si lavora in Pair Programming? Quali sono i suoi vantaggi?

La rotazione regolare dei ruoli (un buon ritmo è generalmente 25 minuti) mantiene attiva la risoluzione dei problemi in coppia, evitando che il processo diventi noioso per il navigator. È fondamentale sottolineare che il Pair Programming è una skill sociale da sviluppare e come tale richiede allenamento e retrospezione per essere migliorata, specialmente quando viene praticata in modalità remota. La sua efficacia deriva da due fattori determinanti:

  1. creazione di un codice di qualità superiore, grazie alla collaborazione e al feedback continuo;
  2. riduzione dei tempi di attesa dovuti alla richiesta di feedback a persone che non stavano sviluppando con te.

C'è una modalità fondamentale connessa al pair programming che eleva la pratica al sistema team, questa pratica si chiama pair negotiation. Pair Negotiation implica la regolare (un buon ritmo è una volta al giorno) rotazione delle coppie di sviluppatori all'interno del team. Questo approccio favorisce la creazione di un senso di ownership collettiva del codice, riducendo la tendenza a identificare un pezzo di codice con un singolo autore. L'ownership collettiva promuove una diffusione naturale dei coding standard e della conoscenza (sia del codice che di dominio) all'interno del team, evitando la necessità di meeting noiosi e ridondanti per discutere tali questioni. In assenza di questa modalità alcuni dei principali benefici del pair programming vengono vanificati.

La pratica del Pair Programming è valida sia tra sviluppatori senior e junior, sia tra due sviluppatori con lo stesso livello di esperienza. Anche se la coppia formata da un senior e un junior può sembrare intuitivamente più fruttuosa per la condivisione di conoscenze, è importante comprendere che il Pair Programming vede la sua massima efficacia tra due sviluppatori con skill di livello simile; il motivo è che l'obiettivo principale è la co-creazione di soluzioni nuove e una coppia con pari seniority è la configurazione in cui nascono generalmente le idee più innovative. È importante sottolineare che, all'interno di un team XP, non esistono gerarchie formali, ma piuttosto una leadership informale e dinamica basata sull'esperienza.

 

Test Driven Development (TDD)

Passiamo ora ad analizzare la seconda pratica del feedback loop breve nell'Extreme Programming, ovvero il Test Driven Development (TDD).

Test Driven Development (TDD), la definizione

Il TDD è una pratica che anticipa la scrittura del test automatico ancor prima di scrivere il codice. È importante sottolineare che il TDD è una pratica di testing ma questo non è il suo obiettivo principale. L'obiettivo primario del TDD è dare feedback sul design che stiamo creando, e proprio per questa ragione generalmente il TDD viene fatto con test unitari. Un codice facilmente testabile durante il processo di TDD è con altissima probabilità un codice disaccoppiato, quindi facilmente modificabile in futuro.

Aspetto da non sottovalutare è che il TDD dà feedback sul design che stiamo creando, ma sta poi allo sviluppatore avere basi di buon design per ascoltare il feedback e applicare dei pattern che lo renderanno facilmente testabile.

 

Come funziona il Test Driven Development (TDD)

Il TDD è un ciclo di feedback brevissimo di sviluppo diviso in tre fasi.

La prima fase prevede la scrittura di un test, che rappresenta la più piccola nuova funzionalità desiderata. Questo test sarà inizialmente "rosso", indicando che la funzionalità non è stata ancora implementata. È interessante notare che questa pratica riflette un principio di Shigeo Shingo, esposto nel libro "Zero Quality Control" degli anni '60, che sottolinea l'importanza di integrare la qualità nel processo di sviluppo ancor prima di realizzare ciò che si vuole. Il codice su cui sviluppiamo spesso è complesso ed ottenere un test fallito, anche se può sembrare semplice, non è per nulla scontato. 

La seconda fase, chiamata "fase maialino", è la realizzazione della più piccola, semplice ed essenziale porzione di codice per far passare il test. Nel TDD si scrive solo ed esclusivamente il codice atto a far passare il test "rosso" che abbiamo precedentemente scritto: ogni codice di produzione deve essere giustificato da un test che fallisce. Si chiama "fase maialino" perché il nostro obiettivo è raggiungere il più rapidamente possibile lo stato "verde", ovvero il superamento del test, dimenticandoci delle regole di buon codice; in questo momento specifico, la qualità può essere temporaneamente sacrificata.

I cicli sono brevi e il test dovrebbe rimanere "rosso" solo per pochissimo tempo, qualche minuto. Se il test rimane in questa fase per più tempo o abbiamo sbagliato test e quindi si cambia, oppure è necessario un refactoring preparatorio per aggiungere le feature con più serenità.

La terza, ed ultima, fase è la fase di refactoring. Il refactoring è anche una pratica dell'XP che vedremo nel capitolo successivo.

test-driven-development-TDD

Che cos'è la pratica di ATDD (Acceptance Test Driven Development)

È importante menzionare la pratica correlata di ATDD (Acceptance Test Driven Development), in cui si inizia con un test di accettazione più ampio, che generalmente rappresenta un intera feature completata. Successivamente, il test di accettazione viene lasciato fallire e il team passa a cicli di TDD più piccoli, contribuendo a guidare il design del software in modo incrementale ed emergente. Una volta terminati i cicli di TDD otterremo un test "verde" di accettazione che certifica la conclusione dell'implementazione della feature.

Gli acceptance test, che mirano a testare la funzionalità dall'esterno, spesso utilizzano test di integrazione o end-to-end, mentre i test TDD si concentrano principalmente su test di unità. Un test di unità è progettato per testare un singolo pezzo di codice circoscritto senza coinvolgere altre parti del sistema.

Per una più dettagliata definizione di test di unità consiglio il libro "Working effectively with legacy code" di Michael Feathers e questo blogpost di approfondimento 

 

Possibili difficoltà nell'adozione di Test Driven Development

Va sottolineato che, in un contesto di codice legacy con elevato debito tecnico, la pratica del TDD può risultare difficoltosa e, talvolta, impraticabile. Tuttavia, implementare il TDD in modo appropriato agevola un design con alta coesione e basso accoppiamento, il che significa un costo del cambiamento basso e costante nel tempo. Quando il costo del cambiamento è basso, il team può evitare i dolori associati a modifiche complesse e costose nel software. In ultima istanza, ma non meno importante, il TDD assicura una test suite automatica che agevola le modifiche in sicurezza.

 

Il Refactoring

Ci siamo lasciati con la terza fase del Test Driven Development (TDD) in sospeso, ovvero il refactoring. Dopo aver passato la fase "maialino" e aver fatto passare il test, entriamo nella fase di miglioramento del codice e del design. È importante notare che il refactoring implica la modifica della struttura del codice senza alterarne il comportamento. Questa pratica è posta come ultima fase del ciclo perché, grazie ai test precedentemente scritti, è possibile effettuare modifiche al codice senza introdurre errori nel comportamento dato che i test fungeranno da rete di salvezza.

Il refactoring, come sottolineato in precedenza, è un processo iterativo ed incrementale che avviene ad ogni micro ciclo di sviluppo software.

Durante il refactoring, il pair programming rivela tutta la sua efficacia. Il pair, agendo come navigator, è fondamentale per valutare se le modifiche stanno portando a miglioramenti effettivi e vanno nella direzione di ottenere un codice facilmente modificabile in futuro. Un modo interessante di unire il TDD con il pair programming è attraverso il "ping-pong programming", dove un programmatore fa il test rosso e l'altro lo fa passare, alternando i ruoli in un processo di collaborazione attiva.

Il refactoring è cruciale per mantenere un codice pulito e maneggevole nel tempo. Deve essere distinto dall'aggiunta di nuove funzionalità: nel contesto di XP, è consigliato alternare costantemente le attività di sviluppo di nuove funzionalità con le fasi di refactoring, evitando di accumulare debito tecnico che potrebbe rendere difficile la manutenzione futura. Con un refactoring costante vogliamo evitare di dover fermare lo sviluppo per rifare il codice perché troppo difficile da maneggiare.

La metafora del "Boy Scout" è spesso menzionata come un principio guida per il refactoring. Il Boy Scout, quando decide di accamparsi, come prima cosa pulisce l'area (refactoring preparatorio), in fine quando decide di andare via disfa il campo e pulisce ulteriormente per lasciare pulito. Questo approccio si applica anche al codice, quando si fa una funzionalità prima si rifattorizza e al termine lo si fa ancora per lasciare il codice ancora migliore di come era prima di iniziare.

Il refactoring diventa particolarmente delicato quando si tratta di codice legacy, ovvero codice con un design con alto accoppiamento, privo di test e fragile. In questi casi, il libro "Working Effectively with Legacy Code" di Michael Feathers fornisce una guida su come gestire il refactoring in modo sicuro. La strategia prevede la creazione di test di caratterizzazione che testano il comportamento del codice legacy dall'esterno, fornendo una rete di sicurezza prima di intraprendere il refactoring vero e proprio.

In sintesi, il refactoring è un'attività essenziale per mantenere un codice sano e facilmente modificabile nel tempo. Sebbene possa sembrare impegnativo, un approccio costante e iterativo può rendere questa pratica non solo efficace ma anche gratificante per gli sviluppatori desiderosi di mantenere un alto standard di qualità nel loro lavoro.

 

La pratica del Simple Design

Ultima pratica che discuteremo in questo articolo è Simple Design. Questa pratica enfatizza che il design del software dovrebbe essere il più semplice ed essenziale possibile (KISS, keep it simple stupid). Contrariamente alla tendenza di alcuni sviluppatori di cercare di prevedere tutte le funzionalità future e di progettare di conseguenza, il Simple Design si concentra solo sulla realizzazione del minimo necessario per soddisfare la più piccola funzionalità che vogliamo rilasciare in produzione per ottenere feedback.

La pratica del Simple Design è strettamente legata al Test Driven Development (TDD) perché attraverso iterazioni di test, sviluppo e refactoring, si sviluppa solo il codice necessario per superare i test: un codice Simple, quindi.

Nel suo libro 4 Rulese of Simple Design, Corey Haynes ha delineato quattro regole del Simple Design.

La prima: 

  1. "I test passano". Questo significa che i test in primis ci sono, e rimangono in stato verde una volta che abbiamo creato un design semplice. Inoltre, il Simple Design segue il principio KISS, acronimo di "Keep it simple, stupid" (mantienilo semplice, stupido). Questo principio sottolinea l'importanza di mantenere il design il più semplice possibile.
  2. Il codice dovrebbe esprimere solo e solamente gli intenti del momento: Il codice dovrebbe essere espressivo, comunicando chiaramente le idee che si stanno implementando in quel momento. Un design semplice permette a chi legge il codice di comprendere gli intenti senza complicazioni.
  3. Un simple design non ha duplicazione (DRY): Segue il principio "Don't Repeat Yourself" (non ripetersi). Il codice dovrebbe evitare la duplicazione, il che significa che la stessa logica dovrebbe essere implementata in un unico punto, rendendo il software più manutenibile.
  4. Il simple design ha quanti meno elementi possibili: Un buon design dovrebbe essere composto da un numero minimo di elementi. Questo contribuisce a mantenere il sistema più chiaro e gestibile nel tempo.

Il Simple Design è connesso a tutte le altre pratiche raccontate nell'articolo:

  • emerge da un'attività di refactoring continua;
  • attraverso il TDD è possibile raggiungere il Simple Design sviluppando in piccoli passi che soddisfano un solo test alla volta;
  • con la pratica del pair programming l'ideazione di design più semplice avviene grazie alla collaborazione tra due persone.

In sintesi, il Simple Design è una pratica dell'Extreme Programming (XP) che promuove la creazione di software efficiente e manutenibile, concentrandosi sul soddisfare solo le esigenze immediate con un design semplice. È l'essenzialità messa nel codice.

Nel prossimo articolo inizieremo analizzeremo le pratiche del ciclo intermedio insieme a Nicola Moretto di QMates. A questo link trovi la precedente puntata eXtreme Programming: che cos'è e come funziona

Similar posts