Il potere degli esempi
Summary: executable acceptance tests are an essential part of any software specification. Even the use of formal specifications in the form of mathematical formulae does not eliminate the need for executable acceptance tests.
Example isn’t another way to teach, it is the only way to teach
— attribuito ad Albert Einstein
Ci sono essenzialmente due maniere di scrivere una specifica per un programma: in linguaggio naturale, oppure usando un linguaggio formale. La tesi di questo articolo è che né l’uno né l’altro stile sono efficaci se non sono accompagnati da esempi. Anzi, è molto più fruttuoso pensare a una specifica come un insieme di esempi, corroborati da una descrizione in linguaggio naturale e/o formale.
Ci sono alcuni problemi che sono molto facili da specificare in linguaggio naturale; ad esempio “sostituisci ogni sequenza di due o più lettere uguali con una sola lettera”. Eppure non siamo sicuri di avere capito che cosa si intende fino a quando non abbiamo visto almeno un paio di esempi:
"abc" -> "abc" "aaaabbcccccd" -> "abcd"
Solo ora possiamo essere sicuri di avere capito che cosa intendeva la specifica. E’ vero che la specifica originale era ambigua; ad esempio non specificava cosa succede per le sequenze di una sola lettera, e neppure diceva quale lettera deve essere sostituita al posto della sequenza; e si potrebbe continuare. Potremmo allora rendere la specifica in linguaggio naturale più precisa, specificando in maggiore dettaglio che cosa deve succedere in tutti i casi. Probabilmente la specifica diventerebbe dieci volte più lunga. Prova! Il risultato sarà senz’altro più preciso; ma sarà anche più chiaro? Probabilmente no; la specifica più diventa lunga e dettagliata e più diventa difficile da capire. In ogni caso saremo costretti a verificare se la nostra comprensione della specifica è corretta andando a vedere gli esempi.
E qui ti racconto un piccolo sporco segreto di noi programmatori: quando riceviamo un tomo di specifica di qualche centinaio di pagine, andiamo a vedere solo gli esempi. Se ci sono dei disegni che mostrano l’interfaccia utente, andiamo a vedere subito i disegni, ignorando il testo che li accompagna. Perché? Perché normalmente le specifiche in linguaggio naturale, nell’intento di essere precise, sono verbose e noiose, ma soprattutto sono poco chiare. Comunicano poco. Niente comunica in maniera efficace come un esempio.
Il nostro spirito scientifico-ingegneristico si ribella allora a questa situazione di pressapochismo. Giustamente cerchiamo una maniera più precisa e meno ambigua di specificare un software che, oltre a tutto, è di norma oggetto di obblighi contrattuali. Come potremmo specificare formalmente, allora, il problema di cui sopra?
Una maniera è scrivere una formula matematica. Però mi risulta difficile immaginare quale, per questo specifico esempio. Provo:
Sia * l’operatore che prende due lettere uguali adiacenti in una stringa e le sostituisce con una delle due;
sia P l’operatore che prende un operatore e lo applica a una stringa fino a quando è possibile. Allora la nostra funzione f è specificata da
f = P*
Ummmm…. non molto chiaro vero? Provo in un’altra maniera. Definiamo il significato di “comprimere le sequenze” con una funzione definita ricorsivamente:
f [] = [] -- trasformo la stringa vuota in stringa vuota f [a] = [a] -- e il singleton in singleton f ([a,a] + x) = f([a] + x) -- se la stringa inizia con due caratteri -- uguali, ne tolgo uno f ([a,b] + x) = [a] + f([b] + x) if a != b -- se la stringa inizia con due caratteri -- diversi, tengo il primo e continuo
Personalmente mi piace questa definizione. E’ chiara e precisa, e anche ragionevolmente concisa. Ma è un programma. E non è immediatamente evidente che corrisponda alla mia idea di cosa significa “comprimere le sequenze”. L’unica maniera di essere sicuro è testare questa specifica su esempi! E si noti che sto proponendo questa formulazione come la specifica del problema. Perché è la specifica formale più chiara che sono riuscito a scrivere. Quindi non posso verificare che questa definizione corrisponda a una qualche specifica, perché è questa stessa la specifica. Potrei verificare se questa definizione ricorsiva corrisponde a un’altra definizione matematica, ma allora avrei sempre il problema di capire se quest’altra ipotetica specifica è a sua volta corretta. E potrei farlo solo testandola su esempi!
Ora, Dijkstra sicuramente mi direbbe che
Program testing can be used to show the presence of bugs, but never to show their absence!
Però Dijkstra, per quanto ho letto o sentito dalle sue labbra, sorvolava sul problema di come ottenere una specifica corretta. Lui dava per scontato che sia sempre possibile scrivere una specifica formale che sia sufficientemente chiara da essere evidentemente corretta. Io invece sostengo che questa cosa non è facile, e probabilmente non è possibile in pratica nella maggior parte dei casi. A meno di lavorare in domini molto particolari che beneficiano di migliaia di anni di raffinamento come, ad esempio, i programmi che fanno calcoli matematici. Sicuramente nel software di tipo gestionale, la cosa più difficile è catturare specifiche corrette. Ma soprattutto, una specifica formale difficilmente può essere capita dal cliente; non così gli esempi, che possono essere verificati, anzi, dettati dal cliente stesso.
E qui entra il discorso dei test di accettazione automatici. Nei metodi agili vengono spesso detti customer tests, perché vengono scritti con o addirittura dal cliente. Sono esempi di come deve comportarsi il software, espressi nel linguaggio di business del cliente e non nel linguaggio tecnico dello sviluppatore. Lo scopo dei customer test non è tanto di verificare che il software sia stato realizzato correttamente; anche questo, senz’altro, ma il punto fondamentale è verificare che il software che realizziamo sia il software che veramente il cliente vuole.
Quando si inizia l’intervista per raccogliere i requisiti per un nuovo progetto, il cliente di solito ce ne dà fin troppi. Per esempio, il problema di calcolare la tariffa di un noleggio auto è senz’altro complicato: dipende dal modello dell’auto, dal chilometraggio, dall’età del guidatore, dall’ora di prelevamento e di consegna, distinguendo fra tariffa notturna e diurna; e da chissà quanti altri dettagli. L’unica maniera di essere sicuri di avere afferrato correttamente le specifiche del nostro cliente è di sottoporgli degli esempi concreti. Dunque, se ho capito correttamente, un guidatore di 30 anni prende una Sedici lunedì mattina alle 10:00 e la restituisce martedì alle 18:00 con il serbatio pieno. Allora si applica la tariffa standard per 1 giorno + 8 ore? E il cliente stesso a questo punto ci dirà se abbiamo capito. Il cliente debugga la comprensione della specifica da parte dello sviluppatore.
Un team agile catturerà questi esempi in test che siano sia eseguibili che comprensibili dal cliente. Spesso si usano strumenti come Fitnesse per catturare le specifiche in tabelle HTML come questa:
eg.Division | ||
numerator | denominator | quotient? |
10 | 2 | 5 |
12.6 | 3 | 4.2 |
100 | 4 | 24 |
Il cliente può così non solo verificare che il team ha capito di cosa si sta parlando; può chiarificare a sè stesso quali siano i requisiti; e può verificare egli stesso, eseguendo il test, che il software consegnato si comporti correttamente sugli esempi:
eg.Division | ||
numerator | denominator | quotient? |
10 | 2 | 5 |
12.6 | 3 | 4.2 |
100 | 4 | 24 expected
25.0 actual |
Dunque gli esempi eseguibili hanno un pubblico molteplice:
- il cliente che li può leggere e anche scrivere
- gli sviluppatori, che li prendono come specifica;
- il software da sviluppare, che da essi viene testato;
- e da ultimo, purtroppo, vanno menzionati anche il giudice, il CTU e i CTP che, nel malaugurato caso di una lite, avranno una molto migliore probabilità di capire che cosa si suppone che il sistema faccia.
Anche se la presenza di una specifica ben scritta (cioè basata su esempi) rende meno probabile l’eventualità di una lite. Nel nostro mestiere il più delle liti sorge su questioni del tipo “intendevo che mi sviluppaste questo”, “no, non era questo che avevi detto!”. Sviluppare la specifica di concerto per mezzo di esempi serve anche a scongiurare esiti di questo tipo.
La mia conclusione è che la maniera tradizionale di presentare le specifiche come “testo eventualmente accompagnato da esempi” oppure “formule matematiche eventualmente corredate da esempi” debba essere scaravoltata.
Gli esempi sono la parte più importante della specifica.
Corredarli con testo esplicativo oppure un modello formale può essere utile; ma dovendo buttare via qualcosa, gli esempi sono l’ultima cosa a cui rinunciare.
January 15th, 2007 at 09:24
Ottimo post, lo sto consigliando in giro ad amici e colleghi.
Un documento di specifica ben fatto (e soprattutto facile da leggere) dovrebbe proprio cominciare con una breve descrizione, alcuni esempi, ed infine un testo esplicativo.
Fermo restando (e qui ti parlo per esperienza personale) che gli esempi *devono* essere corretti e coerenti tra loro e soprattutto, in mancanza del testo esplicativo, devono essere completi e non equivocabili. Altrimenti i programmatori finiscono per passare più tempo a capire le intenzioni del cliente che a sviluppare codice … :)
January 19th, 2007 at 14:55
Il grande Brian Marick sta addirittura scrivendo un libro sul tema, mettendo on-line i primi draft.
Inoltre tutta la sua attività di consulenza ha come mission e fondamento oltre che il testing, proprio “the use of examples to drive projects”.
Che dire quindi: Matteo, siamo in ottima compagnia :)