Questa limitazione di Test Driven Development (e Agile in generale) è praticamente rilevante?


30

In Test Driven Development (TDD) si inizia con una soluzione non ottimale e quindi si producono in modo iterativo quelli migliori aggiungendo casi di test e refactoring. I passaggi dovrebbero essere piccoli, il che significa che ogni nuova soluzione si troverà in qualche modo nelle vicinanze di quella precedente.

Questo assomiglia a metodi matematici di ottimizzazione locale come la discesa del gradiente o la ricerca locale. Una limitazione ben nota di tali metodi è che non garantiscono di trovare l'ottimale globale, o addirittura un accettabile locale ottimale. Se il tuo punto di partenza è separato da tutte le soluzioni accettabili da una vasta regione di soluzioni sbagliate, è impossibile arrivarci e il metodo fallirà.

Per essere più specifici: sto pensando a uno scenario in cui hai implementato una serie di casi di test e poi scopri che il prossimo caso di test richiederebbe un approccio diversamente diverso. Dovrai buttare via i tuoi lavori precedenti e ricominciare da capo.

Questo pensiero può effettivamente essere applicato a tutti i metodi agili che procedono a piccoli passi, non solo a TDD. Questa proposta di analogia tra TDD e ottimizzazione locale ha qualche grave difetto?


Ti riferisci alla sub-tecnica TDD chiamata triangolazione ? Per "soluzione accettabile", intendi una corretta o mantenibile / elegante / leggibile?
guillaume31,

6
Penso che questo sia un vero problema. Dal momento che è solo la mia opinione, non scriverò una risposta. Ma sì, poiché TDD è pubblicizzato come una pratica di progettazione , è un difetto che può portare a massimi locali o nessuna soluzione. Direi che in generale TDD NON è adatto per la progettazione algoritmica. Vedi la discussione correlata sui limiti di TDD: Risolvere il Sudoku con TDD , in cui Ron Jeffries si fa un culo mentre corre in cerchio e "fa TDD", mentre Peter Norvig fornisce la soluzione effettiva conoscendo effettivamente l'argomento,
Andres F.

5
In altre parole, offrirei l'affermazione (si spera) non controversa che TDD sia buono per ridurre al minimo la quantità di classi che scrivi in ​​problemi "noti", quindi producendo codice più pulito e più semplice, ma non è adatto per problemi algoritmici o per problemi complessi in cui effettivamente guardare il quadro generale e avere una conoscenza specifica del dominio è più utile che scrivere test frammentari e "scoprire" il codice che devi scrivere.
Andres F.

2
Il problema esiste, ma non è limitato a TDD o anche Agile. La modifica dei requisiti che implica la progettazione di software precedentemente scritto deve cambiare continuamente.
Remco Gerlich,

@ guillaume31: non necessariamente triangolazione ma qualsiasi tecnica che utilizza iterazioni a livello di codice sorgente. Per soluzione accettabile intendo uno che supera tutti i test e può essere mantenuto ragionevolmente bene ..
Frank Puffer

Risposte:


8

Una limitazione ben nota di tali metodi è che non garantiscono di trovare l'ottimale globale, o addirittura un accettabile locale ottimale.

Per rendere il confronto più adeguato: per alcuni tipi di problemi, è molto probabile che gli algoritmi di ottimizzazione iterativa producano una buona optima locale, per alcune altre situazioni, possono fallire.

Sto pensando a uno scenario in cui hai implementato una serie di casi di test e poi scopri che il prossimo caso di test richiederebbe un approccio diversamente diverso. Dovrai buttare via i tuoi lavori precedenti e ricominciare da capo.

Posso immaginare una situazione in cui ciò può accadere nella realtà: quando si sceglie l'architettura sbagliata in un modo, è necessario ricreare di nuovo tutti i test esistenti da zero. Supponiamo che inizi a implementare i tuoi primi 20 casi di test nel linguaggio di programmazione X sul sistema operativo A. Sfortunatamente, il requisito 21 include che l'intero programma deve essere eseguito sul sistema operativo B, dove X non è disponibile. Pertanto, è necessario eliminare la maggior parte del lavoro e la reimplementazione nella lingua Y. (Ovviamente, non si eliminerebbe completamente il codice, ma lo si trasferirà nella nuova lingua e sistema.)

Questo ci insegna, anche quando si utilizza TDD, è una buona idea fare prima qualche analisi generale e progettazione. Questo, tuttavia, è vero anche per qualsiasi altro tipo di approccio, quindi non lo vedo come un problema intrinseco di TDD. E, per la maggior parte delle attività di programmazione del mondo reale, puoi semplicemente scegliere un'architettura standard (come il linguaggio di programmazione X, il sistema operativo Y, il sistema di database Z sull'hardware XYZ) e puoi essere relativamente sicuro che una metodologia iterativa o agile come TDD non ti porterà in un vicolo cieco.

Citando Robert Harvey: "Non è possibile far crescere un'architettura dai test unitari". O pdr: "TDD non solo mi aiuta a raggiungere il miglior progetto finale, mi aiuta ad arrivarci in meno tentativi."

Quindi in realtà quello che hai scritto

Se il tuo punto di partenza è separato da tutte le soluzioni accettabili da una vasta regione di soluzioni sbagliate, è impossibile arrivarci e il metodo fallirà.

potrebbe diventare vero: quando si sceglie un'architettura sbagliata, è probabile che non si raggiunga la soluzione richiesta da lì.

D'altra parte, quando si esegue in anticipo una pianificazione generale e si sceglie l'architettura giusta, utilizzare TDD dovrebbe essere come avviare un algoritmo di ricerca iterativo in un'area in cui ci si può aspettare di raggiungere il "massimo globale" (o almeno un massimo abbastanza buono ) in pochi cicli.


20

Non penso che TDD abbia un problema di massimi locali. Il codice che scrivi potrebbe, come hai notato correttamente, ma è per questo che è attivo il refactoring (riscrittura del codice senza cambiare funzionalità). Fondamentalmente, con l'aumentare dei test, è possibile riscrivere porzioni significative del modello a oggetti se necessario mantenendo il comportamento invariato grazie ai test. I test affermano verità invarianti sul tuo sistema che, pertanto, devono essere valide sia in termini massimi locali che assoluti.

Se sei interessato a problemi legati al TDD, posso menzionarne tre diversi a cui penso spesso:

  1. Il problema della completezza : quanti test sono necessari per descrivere completamente un sistema? La "codifica per casi esemplificativi" è un modo completo per descrivere un sistema?

  2. Il problema della tempra : qualunque sia il test di interfaccia, deve avere un'interfaccia immutabile. I test rappresentano verità invarianti , ricorda. Sfortunatamente queste verità non sono affatto conosciute per la maggior parte del codice che scriviamo, nella migliore delle ipotesi solo per oggetti rivolti verso l'esterno.

  3. Il problema del danno di prova : per rendere verificabili le asserzioni, potrebbe essere necessario scrivere un codice non ottimale (meno performante, ad esempio). Come possiamo scrivere i test in modo che il codice sia buono come può essere?


Modificato per rispondere a un commento: ecco un esempio di modifica del massimo locale per una funzione "doppia" tramite refactoring

Test 1: quando l'ingresso è 0, restituisce zero

Implementazione:

function double(x) {
  return 0; // simplest possible code that passes tests
}

Refactoring: non necessario

Test 2: quando l'ingresso è 1, restituisce 2

Implementazione:

function double(x) {
  return x==0?0:2; // local maximum
}

Refactoring: non necessario

Test 3: quando l'ingresso è 2, restituisce 4

Implementazione:

function double(x) {
  return x==0?0:x==2?4:2; // needs refactoring
}

refactoring:

function double(x) {
  return x*2; // new maximum
}

1
Quello che ho sperimentato, tuttavia, è che il mio primo progetto ha funzionato solo per alcuni casi semplici e in seguito mi sono reso conto che avevo bisogno di una soluzione più generale. Lo sviluppo della soluzione più generale ha richiesto più test mentre i test originali per i casi speciali non funzioneranno più. Ho trovato accettabile rimuovere (temporaneamente) quei test mentre sviluppo la soluzione più generale, aggiungendoli nuovamente quando il tempo è pronto.
5gon12eder

3
Non sono convinto che il refactoring sia un modo per generalizzare il codice (ovviamente al di fuori dello spazio artificiale dei "modelli di progettazione") o sfuggire ai massimi locali. Il refactoring riordina il codice, ma non ti aiuterà a scoprire una soluzione migliore.
Andres F.

2
@Sklivvz Capito, ma non penso che funzioni così al di fuori di esempi di giocattoli come quelli che hai pubblicato. Inoltre, ti ha aiutato a definire la tua funzione "double"; in un modo che già conoscevi la risposta. TDD aiuta sicuramente quando conosci più o meno la risposta ma vuoi scriverla "in modo pulito". Non sarebbe utile per scoprire algoritmi o scrivere codice davvero complesso. Questo è il motivo per cui Ron Jeffries non è riuscito a risolvere il Sudoku in questo modo; non è possibile implementare un algoritmo che non si ha familiarità con TDD per oscurità.
Andres F.

1
@VaughnCato Ok, ora sono nella posizione di fidarmi di te o di essere scettico (il che sarebbe scortese, quindi non facciamolo). Diciamo solo, nella mia esperienza, non funziona come dici tu. Non ho mai visto un algoritmo ragionevolmente complesso evoluto da TDD. Forse la mia esperienza è troppo limitata :)
Andres F.

2
@Sklivvz "Fintanto che puoi scrivere i test appropriati" è proprio il punto: mi sembra di farmi una domanda. Quello che sto dicendo è che spesso non puoi . Pensare a un algoritmo o a un risolutore non è più facile scrivendo prima i test . È necessario guardare l'intero quadro prima . Ovviamente è necessario provare scenari, ma nota che TDD non riguarda la scrittura di scenari: TDD riguarda il collaudo del progetto ! Non puoi guidare il design di un risolutore di Sudoku (o un nuovo risolutore per un gioco diverso) scrivendo prima i test. Come prova aneddotica (che non è abbastanza): Jeffries non poteva.
Andres F.

13

Quello che stai descrivendo in termini matematici è ciò che chiamiamo dipingere te stesso in un angolo. Questo evento non è certo esclusivo di TDD. In cascata puoi raccogliere e riversare i requisiti per mesi sperando di poter vedere il massimo globale solo per arrivarci e realizzare che esiste un'idea migliore solo la collina successiva.

La differenza è in un ambiente agile che non ti saresti mai aspettato di essere perfetto a questo punto, quindi sei più che pronto a lanciare la vecchia idea e passare alla nuova idea.

Più specifico per TDD, esiste una tecnica per impedire che ciò accada a te quando aggiungi funzionalità in TDD. È la premessa per la trasformazione . Laddove TDD ha un modo formale per il refactoring, questo è un modo formale per aggiungere funzionalità.


13

Nella sua risposta , @Sklivvz ha sostenuto in modo convincente che il problema non esiste.

Voglio sostenere che non importa: la premessa fondamentale (e ragion d'essere) delle metodologie iterative in generale e Agile e in particolare TDD in particolare, è che non solo l'ottimale globale, ma anche gli ottimum locali non lo so. Quindi, in altre parole: anche se quello fosse un problema, non c'è modo di aggirarlo comunque nel modo iterativo. Supponendo che accetti la premessa di base.


8

Le pratiche TDD e Agile possono promettere di produrre una soluzione ottimale? (O anche una soluzione "buona"?)

Non esattamente. Ma non è questo il loro scopo.

Questi metodi forniscono semplicemente un "passaggio sicuro" da uno stato a un altro, riconoscendo che i cambiamenti richiedono tempo, sono difficili e rischiosi. E il punto di entrambe le pratiche è garantire che l'applicazione e il codice siano entrambi fattibili e comprovati per soddisfare i requisiti più rapidamente e più regolarmente.

... [TDD] si oppone allo sviluppo del software che consente di aggiungere software che non ha dimostrato di soddisfare i requisiti ... Kent Beck, a cui è stato accreditato di aver sviluppato o "riscoperto" la tecnica, ha dichiarato nel 2003 che TDD incoraggia semplici progetta e ispira fiducia. ( Wikipedia )

TDD si concentra sul garantire che ogni "blocco" di codice soddisfi i requisiti. In particolare, aiuta a garantire che il codice soddisfi i requisiti preesistenti, anziché consentire ai requisiti di essere guidati da una codifica scadente. Tuttavia, non promette che l'implementazione sia "ottimale" in alcun modo.

Per quanto riguarda i processi Agile:

Il software di lavoro è la misura principale del progresso ... Alla fine di ogni iterazione, le parti interessate e il rappresentante del cliente esaminano i progressi e rivalutano le priorità al fine di ottimizzare il ritorno sull'investimento ( Wikipedia )

L'agilità non è alla ricerca di una soluzione ottimale ; solo una soluzione funzionante , con l'intento di ottimizzare il ROI . Promette una soluzione funzionante prima piuttosto che dopo ; non "ottimale".

Ma va bene, perché la domanda è sbagliata.

Gli ottimali nello sviluppo del software sono obiettivi sfocati e mobili. I requisiti di solito sono in evoluzione e pieni di segreti che emergono, con tuo grande imbarazzo, in una sala conferenze piena di capi del tuo capo. E la "bontà intrinseca" dell'architettura e della codifica di una soluzione è classificata dalle opinioni divise e soggettive dei tuoi pari e da quella del tuo superiore manageriale - nessuno dei quali potrebbe effettivamente sapere qualcosa sul buon software.

In meno, pratiche TDD e agile riconoscono le difficoltà e tentare di ottimizzare per due cose che sono oggettivi e misurabili: . Lavoro V non-lavoro e Sooner v tardi..

E, anche se abbiamo "lavoro" e "prima" come metriche oggettive, la tua capacità di ottimizzare per loro dipende principalmente dall'abilità e dall'esperienza di una squadra.


Le cose che si potrebbe interpretare come gli sforzi producono ottime soluzioni includono cose come:

eccetera..

Se ciascuna di queste cose effettivamente produca soluzioni ottimali sarebbe un'altra grande domanda da porre!


1
È vero, ma non ho scritto che l'obiettivo di TDD o di qualsiasi altro metodo di sviluppo software sia una soluzione ottimale nel senso di un ottimale globale. La mia unica preoccupazione è che le metodologie basate su piccole iterazioni a livello di codice sorgente potrebbero non trovare alcuna soluzione accettabile (abbastanza buona) in molti casi
Frank Puffer,

@Frank La mia risposta è intesa a coprire sia gli ottimum locali che globali. E la risposta in entrambi i casi è "No, non è per questo che sono progettate queste strategie: sono progettate per migliorare il ROI e mitigare il rischio". ... o qualcosa di simile. E ciò è in parte dovuto alla risposta di Jörg: gli "ottimum" sono obiettivi in ​​movimento. ... farei un ulteriore passo avanti; non solo si stanno muovendo obiettivi, ma non sono interamente obiettivi o misurabili.
svidgen,

@FrankPuffer Forse vale la pena aggiungere un addendum. Ma, il punto di base è, ti stai chiedendo se queste due cose ottengano qualcosa che non sono affatto progettate o destinate a raggiungere. Inoltre, stai chiedendo se possono ottenere qualcosa che non può nemmeno essere misurato o verificato.
svidgen,

@FrankPuffer Bah. Ho provato ad aggiornare la mia risposta per dirlo meglio. Non sono sicuro di averlo migliorato o peggiorato! ... Ma devo scendere da SE.SE e tornare al lavoro.
svidgen,

Questa risposta è ok, ma il problema che ho con esso (come con alcune delle altre risposte) è che "mitigare il rischio e migliorare il ROI" non sono sempre gli obiettivi migliori. In realtà non sono obiettivi da soli. Quando hai bisogno di qualcosa per funzionare, mitigare il rischio non lo taglierà. A volte piccoli passi relativamente non orientati come nel TDD non funzioneranno: minimizzerai il rischio, ma alla fine non raggiungerai nessun punto utile.
Andres F.

4

Una cosa che nessuno ha aggiunto finora è che lo "Sviluppo TDD" che stai descrivendo è molto astratto e irrealistico. Potrebbe essere così in un'applicazione matematica in cui si sta ottimizzando un algoritmo, ma ciò non accade molto nelle applicazioni aziendali su cui la maggior parte dei programmatori lavora.

Nel mondo reale i tuoi test sostanzialmente esercitano e convalidano le regole aziendali:

Ad esempio - Se un cliente è un non fumatore di 30 anni con una moglie e due figli, la categoria premium è "x" ecc.

Non modificherai iterativamente il motore di calcolo premium fino a quando non sarà corretto per molto tempo - e quasi certamente non mentre l'applicazione è attiva;).

Quello che hai effettivamente creato è una rete di sicurezza in modo che quando viene aggiunto un nuovo metodo di calcolo per una particolare categoria di clienti, tutte le vecchie regole non si rompano improvvisamente e non forniscano la risposta sbagliata. La rete di sicurezza è ancora più utile se il primo passo del debug è creare un test (o una serie di test) che riproduca l'errore prima di scrivere il codice per correggere il bug. Quindi, un anno dopo, se qualcuno ricrea accidentalmente il bug originale, il test unitario si interrompe prima ancora che il codice venga archiviato. Sì, una cosa che TDD consente è che ora puoi fare un grande refactoring e riordinare le cose con fiducia ma non dovrebbe essere una parte enorme del tuo lavoro.


1
Innanzitutto, quando ho letto la tua risposta, ho pensato "sì, questo è il punto centrale". Ma dopo aver ripensato di nuovo la domanda, ho pensato che non fosse necessariamente così astratto o irrealistico. Se si sceglie alla cieca l'architettura completamente sbagliata, TDD non lo risolverà, non dopo 1000 iterazioni.
Doc Brown,

@Doc Brown Concordato, non risolverà quel problema. Ma ti fornirà una serie di test che esercitano ogni presupposto e regola aziendale in modo da poter migliorare iterativamente l'architettura. L'architettura così male che ha bisogno di una riscrittura completa per risolvere è molto rara (spero) e anche in quel caso estremo i test delle unità di business rule sarebbero un buon punto di partenza.
mcottle

Quando dico "architettura sbagliata", ho in mente casi in cui bisogna buttare via la suite di test esistente. Hai letto la mia risposta?
Doc Brown,

@DocBrown - Sì, l'ho fatto. Se intendevi "architettura sbagliata" significa "cambia l'intera suite di test", forse avresti dovuto dirlo. La modifica dell'architettura non significa che è necessario eliminare tutti i test se sono basati su regole di business. Probabilmente dovrai cambiarli tutti per supportare eventuali nuove interfacce create e persino riscriverne completamente alcune, ma le Regole aziendali non saranno sostituite da una modifica tecnica in modo che i test rimangano. Certamente gli investimenti in unit test non dovrebbero essere invalidati dall'improbabile possibilità di rovesciare completamente l'architettura
mcottle

certo, anche se è necessario riscrivere ogni test in un nuovo linguaggio di programmazione, non è necessario buttare via tutto, almeno si può eseguire il porting della logica esistente. E sono d'accordo con te al 100% per i principali progetti del mondo reale, i presupposti nella domanda sono abbastanza irrealistici.
Doc Brown,

3

Non penso che si frapponga. La maggior parte dei team non ha nessuno in grado di trovare una soluzione ottimale anche se l'hai scritta sulla lavagna. TDD / Agile non si frapporranno.

Molti progetti non richiedono soluzioni ottimali e quelli che lo fanno, il tempo, l'energia e la concentrazione necessari saranno realizzati in questo settore. Come ogni altra cosa che tendiamo a costruire, in primo luogo, farlo funzionare. Quindi fallo veloce. Potresti farlo con una sorta di prototipo se le prestazioni sono così importanti e quindi ricostruire il tutto con la saggezza acquisita attraverso molte iterazioni.

Sto pensando a uno scenario in cui hai implementato una serie di casi di test e poi scopri che il prossimo caso di test richiederebbe un approccio diversamente diverso. Dovrai buttare via i tuoi lavori precedenti e ricominciare da capo.

Questo potrebbe accadere, ma è più probabile che accada è la paura di cambiare parti complesse dell'applicazione. Non avere alcun test può creare un maggiore senso di paura in questo settore. Uno dei vantaggi di TDD e avere una serie di test è che hai creato questo sistema con l'idea che dovrà essere cambiato. Quando trovi questa soluzione monolitica ottimizzata sin dall'inizio, può essere molto difficile cambiare.

Inoltre, inseriscilo nel contesto della tua preoccupazione per l'ottimizzazione insufficiente e non puoi fare a meno di passare il tempo a ottimizzare le cose che non dovresti avere e a creare soluzioni inflessibili perché eri così iper-concentrato sulle loro prestazioni.


0

Può essere ingannevole applicare un concetto matematico come "ottimale locale" alla progettazione del software. L'uso di tali termini rende lo sviluppo del software molto più quantificabile e scientifico di quanto non sia in realtà. Anche se esistesse "ottimale" per il codice, non abbiamo modo di misurarlo e quindi non abbiamo modo di sapere se lo abbiamo raggiunto.

Il movimento agile fu in realtà una reazione contro la convinzione che lo sviluppo del software potesse essere pianificato e previsto con metodi matematici. Nel bene e nel male, lo sviluppo del software è più un mestiere che una scienza.


Ma è stata una reazione troppo forte? Di certo aiuta in molti casi in cui una rigorosa pianificazione iniziale si è rivelata ingombrante e costosa. Tuttavia, alcuni problemi software devono essere affrontati come un problema matematico e con un design iniziale. Non è possibile TDD. Puoi TDD l'interfaccia utente e la progettazione generale di Photoshop, ma non puoi TDD i suoi algoritmi. Non sono esempi banali come derivare "sum" o "double" o "pow" negli esempi tipici di TDD [1]). Probabilmente non è possibile prendere in giro un nuovo filtro immagine fuori dalla scrittura di alcuni scenari di test; devi assolutamente sederti, scrivere e comprendere le formule.
Andres F.

2
[1] In effetti, sono abbastanza sicuro fibonacci, che ho visto usato come esempio / tutorial TDD, è più o meno una bugia. Sono disposto a scommettere che nessuno ha mai "scoperto" fibonacci o serie simili da TDD. Tutti iniziano a conoscere già fibonacci, che è barare. Se provi a scoprirlo da TDD, probabilmente raggiungerai il vicolo cieco di cui l'OP stava chiedendo: non sarai mai in grado di generalizzare la serie semplicemente scrivendo più test e refactoring - devi applicare la matematica ragionamento!
Andres F.

Due osservazioni: (1) Hai ragione sul fatto che questo può essere ingannevole. Ma non ho scritto che TDD è uguale all'ottimizzazione matematica. L'ho usato solo come analogia o modello. Credo che la matematica possa (e dovrebbe) essere applicata a quasi tutto finché si è consapevoli delle differenze tra il modello e la cosa reale. (2) La scienza (lavoro scientifico) è di solito anche meno prevedibile dello sviluppo del software. E direi anche che l'ingegneria del software è più un lavoro scientifico che un mestiere. I mestieri di solito richiedono molto più lavoro di routine.
Frank Puffer,

@AndresF .: TDD non significa che non devi pensare o progettare. Significa solo che scrivi il test prima di scrivere l'implementazione. Puoi farlo con gli algoritmi.
JacquesB,

@FrankPuffer: OK, quindi quale valore misurabile ha un "ottimale locale" nello sviluppo del software?
Jacques B
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.