Riepilogo
Questa lezione apre l’ottava e ultima unità del corso, dedicata ai programmi più grandi. Ne spiega lo scopo: organizzare, verificare e fare il debug di programmi che non stanno più in un file né nella testa; presenta le sue tre lezioni e il filo che le unisce, che chiude il cerchio aperto nella prima lezione del corso (specificare, costruire, verificare); ricorda ciò che l’unità dà per scontato, cioè il corso intero; formula il problema che l’unità risolve, quello di un programma di un solo file che funziona con i dati con cui è stato testato e tace un difetto al bordo della sua specifica; anticipa la differenza tra testare e dimostrare; e mostra dove conduce, nel progetto finale e in Programmazione in C++ II.
Obiettivi di apprendimento
- Descrivere la sequenza dei contenuti dell’unità e spiegare che cosa ciascuna lezione apporta alla successiva.
- Ricordare, con un esempio, la differenza tra dichiarare e definire una funzione, il contratto che la specifica e le fasi (compilazione e collegamento) che trasformano più file in un programma.
- Anticipare, con un esempio, la differenza tra testare un programma con alcuni casi e dimostrare che è corretto per tutti quelli ammissibili.
- Riconoscere che cosa ci si aspetta di saper fare alla fine dell’unità e del corso, e distinguerlo da ciò che si imparerà in Programmazione in C++ II.
Valutazione d’ingresso
Prima di percorrere l’unità conviene misurare il punto di partenza. Questa valutazione non ha voto: orienta lo studio. Le prime sette domande verificano ciò che l’unità dà per scontato (dichiarare e definire, la precondizione, il contratto di una funzione dell’unità 6, i messaggi del linker, i casi di una ricerca binaria, la tabella di traccia e la lettura fuori da un vettore); le tre successive toccano, nell’ordine delle lezioni, le idee centrali dell’unità, e in esse «non lo so ancora» è una risposta onesta. Ogni risposta spiega il punto e nomina la lezione che lo tratta; le stesse abilità si misurano di nuovo nella lezione di sintesi.
Prima di iniziare l’unità, misura il tuo punto di partenza. È un’autovalutazione breve: non riceve voto e il risultato resta solo nel tuo browser.
Domanda 1Dalle unità precedentiChe cosa fa la riga
int gcd(int a, int b);scritta prima dimain?Confonde dichiarazione e definizione: senza corpo non c’è nulla da eseguire, e nessuna funzione si esegue perché è scritta, ma quando viene chiamata.Confonde dichiarare con chiamare: una chiamata non porta i tipi dei parametri.Ignora che al compilatore basta la firma per controllare una chiamata; la mancanza del corpo, se nessuno lo scrive, la scopre il linker, non il compilatore.Una dichiarazione basta perché il compilatore controlli ogni chiamata; la definizione, con il corpo, può stare più in basso o in un altro file. Tutta la lezione Più file e librerie proprie poggia su questa separazione: l’header dichiara, il file di implementazione definisce.Ripassare: Funzioni
Domanda 2Dalle unità precedentiLa funzione
toBase(n, base)ha la precondizione «n >= 0e2 <= base <= 10». Chi deve garantirla?Confonde la precondizione con la validazione degli input: validare è compito del confine del programma, non di ogni funzione.Crede che i commenti vengano verificati; il compilatore li scarta.Non vede che il contratto ripartisce gli obblighi; senza di esso, nessuno sa chi ha sbagliato quando qualcosa fallisce.Domanda 3Dalle unità precedentiLa funzione
binarySearch(values, target)della lezione 6.4 restituisce una posizione ditarget, ovalues.size()se non c’è. Qual è la sua precondizione?Con il vettore vuoto il ciclo non compie nessuna iterazione e la funzione restituisce 0, che èvalues.size(): il caso è coperto dalla postcondizione.La funzione segnala l’assenza; esigere la presenza la renderebbe inutile.Con elementi ripetuti la funzione restituisce una delle posizioni del valore, il che la postcondizione ammette.Ogni iterazione scarta metà dell’intervallo confrontando con l’elemento centrale, e ciò è valido solo se il vettore è ordinato. Con dati non ordinati la funzione risponde, senza avviso, qualcosa di falso. La precondizione si scrive nel contratto, dove chi chiama la vede.Ripassare: Cercare e ordinare
Domanda 4Dalle unità precedentiUn programma compila senza errori e, nel costruire l’eseguibile, compare
undefined reference to `tetrahedral(int)'. Quale fase è fallita?Non distingue i messaggi del compilatore, che citano un file e una riga, da quelli del linker, che citano oggetti e nomi.Il programma non è mai venuto a esistere: senza eseguibile non c’è esecuzione.Un#includeassente producewas not declared in this scopein compilazione, non questo messaggio.Il compilatore ha accettato ogni chiamata perché ha visto una dichiarazione; il linker ha cercato la definizione in tutti gli oggetti e nelle librerie, e non l’ha trovata. La lezione Più file e librerie proprie ne spiega le cause abituali: un file che non è stato compilato, o una dichiarazione copiata con un altro tipo.Ripassare: Programmi, compilatori e C++
Domanda 5Dalle unità precedentiQuale di questi insiemi di casi mette meglio alla prova una ricerca binaria?
Confonde quantità con copertura: non testa mai l’assenza né i bordi, dove di solito stanno i difetti.Un caso che è già passato è quello che ha meno probabilità di scoprire qualcosa di nuovo.Sono infiniti, o astronomicamente molti entro i limiti del tipo: testare esige di scegliere.Ogni caso esercita un bordo diverso dell’intervallo : l’intervallo vuoto fin dall’inizio, quello che si riduce a un elemento, gli estremi e i tre modi di non esserci. Scegliere i casi a partire dalla specifica, per regioni e bordi, è il tema della lezione Testare.Ripassare: Cercare e ordinare
Domanda 6Dalle unità precedentiQuanto valgono
aebdopoint a{0}; int b{1}; for (int i{0}; i < 4; ++i) { const int t{a + b}; a = b; b = t; }?Conta un’iterazione di troppo: coni < 4il corpo si esegue quattro volte, non cinque.Conta un’iterazione di meno, l’errore di uno (off-by-one) simmetrico.Dimentica chetconserva la somma calcolata prima cheacambi: nell’ultima iterazioneariceve il 3 dib, mabriceve quella somma, 5, e non il nuovo valore dia.Domanda 7Dalle unità precedentiPerché
std::vector<int> v{1, 2}; std::println("{}", v[2]);si ferma con le opzioni del corso e, compilato solo con-O2, senza i sanitizer, può scrivere un numero qualsiasi?Crede che l’ottimizzazione ripari gli errori; toglie solo controlli e presuppone che il programma non violi lo standard.Inverte l’effetto: il controllo che ferma il programma è quello della libreria senza ottimizzazione.È l’errore di uno degli indici: quelli di un vettore di due elementi sono 0 e 1.Domanda 8Un header con guardie di inclusione definisce (non solo dichiara)
double scale(double x), senzainline, e lo includonomain.cppereport.cpp. Che cosa succede nel costruire il programma?Crede che le guardie agiscano tra file diversi; agiscono all’interno di ciascuna unità di traduzione.Confonde il file che si compila con il programma intero: all’interno di ciascun file c’è una sola definizione.È una risposta onesta: la lezione 8.1 lo mostra con il messaggio reale del linker.Le guardie impediscono che un header entri due volte in uno stesso file, ma ogni.cppsi compila separatamente e riceve la sua copia della definizione: il linker ne trova due. È la regola della definizione unica, della lezione Più file e librerie proprie.Ripassare: Più file e librerie proprie
Domanda 9Una funzione supera cinquecento test automatici scelti con cura. Che cosa si può affermare?
Confonde abbondanza con dimostrazione; un difetto in un bordo che nessun caso ha toccato sopravvive a qualsiasi numero di casi interni.I casi non sono un campione casuale dei difetti possibili, e la frequenza dei successi non misura la probabilità che il programma sia giusto.È una risposta onesta: la lezione 8.2 comincia da questa domanda.Un test può mostrare la presenza di un difetto, mai la sua assenza: affermare la correttezza per tutti i casi esige un argomento, come l’invariante della lezione 4.3, non un elenco di casi. I test ben scelti rendono improbabile che un difetto comune passi, e per questo valgono tanto. La lezione Testare sviluppa questa distinzione.Ripassare: Testare
Domanda 10Un punto di interruzione si trova sulla riga
total += k;, dentro un ciclo, e il programma vi si ferma per la terza volta. Che cosa indica la riga evidenziata?Confonde la sosta con il passo già compiuto.Confonde il punto in cui il programma si ferma, che sceglie chi fa il debug, con la causa del malfunzionamento.È una risposta onesta: la lezione 8.3 tratta i punti di interruzione e i passi.
Caricamento del contenuto…