[#_lesson_8_when_it_goes_wrong] == Lezione 8: Quando qualcosa va storto image::learn/08-when-it-goes-wrong.png[La lezione finita, 480] Hai scritto un gioco. Da qui in poi passerai più tempo ad aggiustare programmi che a scriverli, e non è segno che ci sia qualcosa che non va in te. È il mestiere. La differenza tra chi si diverte a programmare e chi ci rinuncia non è quasi mai il talento; è se ha imparato a leggere quello che il computer gli sta dicendo. Quindi in questa lezione non c'è niente di nuovo da costruire. Ci sono i cinque messaggi che vedrai più spesso per il resto della tua vita, che cosa vuol dire davvero ciascuno, e quattro modi per stanare l'errore che c'è dietro. Tieni aperto `dodge.singe` della settima lezione, perché lo romperai apposta parecchie volte. [#_two_kinds_of_stop] === Due tipi di arresto Singe si ferma per due motivi diversi, e lo dice in due modi diversi. Quando lo script non parte nemmeno, ottieni questa forma: ---- Error running script: dodge/dodge.singe:92: 'then' expected near 'spawnBlock' ---- Lua legge tutto il tuo file prima di eseguirne una sola parola, e questo vuol dire che non è riuscito a dare un senso a ciò che ha letto. Non è stato eseguito niente. Quando lo script è partito bene ma qualcosa è andato storto dopo, ottieni invece questa forma: ---- Error executing function 'onOverlayUpdate': dodge/dodge.singe:98: attempt to perform arithmetic on a nil value (global 'blockSped') stack traceback: dodge/dodge.singe:98: in function 'updatePlaying' dodge/dodge.singe:136: in function 'onOverlayUpdate' ---- Il gioco stava girando. Singe ha chiamato la tua `onOverlayUpdate`, come fa sessanta volte al secondo, e questa volta la chiamata non è tornata. Entrambe le forme contengono le stesse tre cose, e sono proprio le tre cose che vuoi: un file, una riga e una lamentela. Leggile in quest'ordine, ogni volta. I numeri di riga di questa lezione vengono dal listato finito della settima lezione. Se i tuoi sono spostati di una riga o due, lo è anche il tuo file, e niente nel leggere un errore dipende dal fatto che il numero corrisponda. [#_the_ones_that_stop_it_before_it_starts] === Quelli che lo fermano prima che parta Questi sono quelli facili, e sono anche quelli che fanno sentire peggio, perché lo schermo resta nero e non c'è niente da guardare. Vai in `updatePlaying` e cancella la parola `then` dalla riga `if spawnTimer <= 0 then`. Salva: ---- Error running script: dodge/dodge.singe:92: 'then' expected near 'spawnBlock' ---- Lua è arrivato alla fine della riga con `if`, voleva la parola `then` e invece ha trovato l'inizio della riga successiva -- che è `spawnBlock()`, ed è quella la parte `near 'spawnBlock'`. «Near» vuol dire «è qui che me ne sono accorto», e di solito è la prima cosa *dopo* quello che hai dimenticato. Ora una più cattiva. Rimetti `then`, e cancella invece la parola `end` sull'ultima riga di `spawnBlock`. Salva: ---- Error running script: dodge/dodge.singe:148: 'end' expected (to close 'function' at line 58) near ---- La riga 148 è la fine del file, appena dopo la sua ultima riga. Non c'è niente di sbagliato nella riga 148. Lua ha letto fino in fondo -- `` è il modo in cui chiama la fine di un file -- sempre aspettando un `end` che non è mai arrivato, e a quel punto aveva inghiottito ogni funzione dopo `spawnBlock` come se ne facesse parte. La metà utile di quel messaggio è `(to close 'function' at line 58)`, che è dove comincia `spawnBlock`. Quando un messaggio punta alla fine del file, credi a quello che dice tra parentesi, non al numero di riga. È anche per questo che il rientro conta così tanto: un `end` mancante è quasi invisibile in un elenco di righe, ed evidente nel momento in cui tutto ciò che sta sotto è rientrato di un passo di troppo. Altri due che vale la pena saper riconoscere, perché li incontrerai entrambi questa settimana: ---- Error running script: dodge/dodge.singe:41: unfinished string near '"GAME OVER)' ---- Una virgoletta che hai aperto e mai chiuso, che hai incontrato nella prima lezione. ---- Error running script: dodge/dodge.singe:135: 'then' expected near '=' ---- Questo è `if state = "playing" then`. Un `=` imposta una variabile, due `==` fanno una domanda, e Lua non ti lascia impostare una variabile dentro un `if`. Sembra una lamentela strana, e vuol dire sempre la stessa cosa. [#_attempt_to_index_a_nil_value] === Attempt to index a nil value Questo è quello che vedrai più spesso, e di gran lunga. Vai in `spawnBlock` e scrivi male `block` nella prima riga che lo usa, così che diventi `blocl.x = math.random(0, screenWidth - BLOCK_SIZE)`. Salva, premi la barra spaziatrice: ---- Error executing function 'onOverlayUpdate': dodge/dodge.singe:60: attempt to index a nil value (global 'blocl') stack traceback: dodge/dodge.singe:60: in function 'spawnBlock' dodge/dodge.singe:92: in function 'updatePlaying' dodge/dodge.singe:136: in function 'onOverlayUpdate' ---- *Indicizzare* è ciò che fa il punto. `blocl.x` vuol dire «entra nella cosa chiamata `blocl` e trovaci la `x`». Puoi farlo solo con una tabella. *Nil* è il nome che Lua dà a un nome senza niente dietro. Non l'hai visto molto, perché sei stato attento a dare un valore a tutto, ma ogni nome che non hai mai usato è già `nil`, e scrivere male un nome ne inventa uno nuovo di zecca. Quindi il messaggio intero dice: *hai usato un punto su qualcosa che non esiste*. La parte tra parentesi è il regalo. `global 'blocl'` è il nome esatto della cosa che era vuota. Cercalo nel tuo file. Se compare una volta sola, è un errore di battitura, e l'hai trovato in dieci secondi. La parola prima del nome ti dice dove l'ha cercato Lua: * `global` -- un nome senza un `local` davanti, da nessuna parte. * `local` -- un nome che hai dichiarato con `local` nella funzione in cui ti trovi. * `field` -- un nome dopo un punto, come la `y` in `block.y`. * `upvalue` -- una variabile `local` di un livello più esterno che questa funzione riesce a vedere, ed è ciò che sono tutte le variabili di `dodge.singe`, visto che sono dichiarate in cima al file e usate dentro le funzioni. `field` è l'altra causa comune, e non è affatto un errore di battitura. Se hai scritto `block.hit.when` e non hai mai impostato `block.hit`, ottieni `attempt to index a nil value (field 'hit')`. Il punto che fallisce è il secondo. Lua ti dice quale, nominandolo. [#_attempt_to_call_a_nil_value] === Attempt to call a nil value Rimetti a posto `blocl`. Ora scrivi male una funzione del motore: in `drawGame`, cambia `overlayPrint` in `overlayPirnt`. Salva: ---- Error executing function 'onOverlayUpdate': dodge/dodge.singe:36: attempt to call a nil value (global 'overlayPirnt') stack traceback: dodge/dodge.singe:36: in function 'drawGame' dodge/dodge.singe:140: in function 'onOverlayUpdate' ---- Stesso `nil`, crimine diverso. *Chiamare* è ciò che fanno le parentesi, e puoi farlo solo con una funzione. Non esiste nessuna funzione chiamata `overlayPirnt`, quindi `overlayPirnt` è `nil`, quindi le parentesi non hanno niente da chiamare. Succede per quattro motivi, in ordine di frequenza: . Hai scritto male una delle tue funzioni. . Hai scritto male una di quelle di Singe. Niente nel motore controlla come scrivi quando il file viene caricato, quindi un nome del motore scritto male è solo un altro nome che si trova a essere vuoto, e non ti costa niente finché la riga non viene eseguita. . La funzione non esiste proprio in Singe -- era un'ipotesi ragionevole, e le ipotesi ragionevoli sono sbagliate più o meno una volta su due. L'elenco delle funzioni del Riferimento è l'unico modo per esserne sicuro. . L'hai resa `local` e l'hai chiamata da un punto che sta sopra quello in cui è dichiarata. È esattamente per questo che le funzioni di `dodge.singe` sono globali. C'è una versione molto peggiore di questo errore, e l'hai incontrata nella prima lezione senza saperlo: scrivi male il nome di una *callback* e non ottieni nessun messaggio. `onOverlayUpdated` non è un errore. È una funzione perfettamente valida di cui Singe non ha mai sentito parlare e che non chiamerà mai, quindi lo schermo resta nero e niente si lamenta. Quando un'intera funzionalità non fa assolutamente niente, sospetta del nome. [#_attempt_to_perform_arithmetic_on_a_nil_value] === Attempt to perform arithmetic on a nil value Rimetti a posto `overlayPrint`. In `updatePlaying`, scrivi male `blockSpeed` nella riga della caduta, così che diventi `block.y = block.y + blockSped`: ---- Error executing function 'onOverlayUpdate': dodge/dodge.singe:98: attempt to perform arithmetic on a nil value (global 'blockSped') stack traceback: dodge/dodge.singe:98: in function 'updatePlaying' dodge/dodge.singe:136: in function 'onOverlayUpdate' ---- L'aritmetica è `+`, `-`, `*` e `/`. Non puoi sommare il niente a un numero. Di nuovo le parentesi nominano la cosa vuota, e di nuovo la causa più comune è un errore di battitura -- ma questo ha una seconda causa che vale la pena conoscere, perché è più difficile da vedere. Una variabile a cui non hai mai dato un numero è `nil`, non zero. Se aggiungi al gioco un punteggio record e scrivi `local hiScore` in cima senza `= 0`, contiene `nil` finché qualcosa non la imposta, e il primo `hiScore + score` ferma il gioco. Lua non fa partire i numeri da zero per te. Sei tu a farli partire da zero. Due parenti stretti, con la stessa causa e la stessa cura: ---- dodge/dodge.singe:37: attempt to concatenate a nil value (upvalue 'hiScore') ---- `..` che attacca una stringa a qualcosa che non c'è. Questo lo vedrai ogni volta che stampi un punteggio che hai dimenticato di impostare -- qui, `hiScore` aggiunto alla riga del punteggio in `drawGame`, che ora è la riga 37 visto che sopra c'è `local hiScore`. ---- dodge/dodge.singe:105: attempt to compare nil with number ---- È `if score > hiScore then`, messo sotto `state = "over"` per tenere il punteggio migliore quando una partita finisce, senza niente in `hiScore`. Nota che questo messaggio non nomina niente tra parentesi. Quando Lua non sa dirti quale lato era vuoto, la sezione dopo la prossima ti spiega come scoprirlo. [#_an_argument_of_the_wrong_kind] === Un argomento del tipo sbagliato Finora tutti i messaggi venivano da Lua. Questo viene da Singe, e ha un aspetto completamente diverso, quindi vale la pena incontrarlo apposta. Supponi di voler vedere che cosa c'è in `blocks`, e di provare a stamparlo. Aggiungi questa riga a `drawGame`, sotto il punteggio: [source,lua] ---- overlayPrint(1, 3, blocks) ---- ---- 37:overlayPrint: Argument 3 must be a string. ---- Niente nome del file, niente traceback, nessun `attempt to` qualcosa. Una lamentela del motore comincia con il numero di riga, poi il nome della funzione che si è rifiutata, poi che cosa voleva. Qui a `overlayPrint` è stata passata una tabella dove serve del testo, e si è fermata invece di disegnare qualcosa senza senso. Il messaggio fratello conta invece di controllare: ---- 34:overlayBox: Expected 4 argument(s), got 3. ---- È un `overlayBox` a cui manca un angolo. Lo strano `argument(s)` è del motore, non un tuo errore di battitura. Quando ottieni uno di questi, la risposta è la voce di quella funzione nel Riferimento, e ti ci vorrà un minuto. Ogni voce elenca gli argomenti in ordine, con che cosa deve essere ciascuno. Singe li controlla perché l'alternativa è un gioco che gira per dieci minuti e poi disegna spazzatura. [#_finding_out_what_is_really_in_there] === Scoprire che cosa c'è davvero lì dentro Metà del debug non è affatto leggere errori. È il caso in cui niente va in crash e niente funziona: blocchi che non compaiono mai, un giocatore che non si muove, un punteggio fermo a zero. Per questo devi vedere dentro il gioco mentre gira, e ci sono due modi. Il primo è `debugPrint`, che scrive una riga nel terminale da cui hai avviato Singe. Prendi la riga che non riesci a capire e mettine una sopra: [source,lua] ---- debugPrint("blocks " .. #blocks .. ", timer " .. spawnTimer) ---- Ora stai guardando quello che c'è davvero invece di quello che sei sicuro che ci sia, ed è in quella distanza che vivono i bug. Stampa `playerX` allo stesso modo e vedrai la verità anche su di lui: viene stampato come `160.0`, non `160`, perché in Lua dividere con `/` dà sempre un numero con una parte decimale, anche quando la parte decimale è zero. Due cose ti morderanno. La prima è che `debugPrint` vuole per forza del testo o un numero; dagli qualsiasi altra cosa e ferma il gioco come ha fatto `overlayPrint`. `debugPrint(blocks)` non ti mostrerà un elenco, e `debugPrint("left " .. goingLeft)` in cima a `updatePlaying` non ti mostrerà nemmeno un `true` o un `false`: ---- dodge/dodge.singe:78: attempt to concatenate a boolean value (upvalue 'goingLeft') ---- La soluzione è `tostring`. Prende qualsiasi cosa e te ne dà una versione in testo, quindi `tostring(goingLeft)` è `"true"` o `"false"` e la riga funziona. La seconda è che `onOverlayUpdate` gira sessanta volte al secondo, quindi un `debugPrint` lì dentro stampa sessanta righe al secondo e non riesci a leggerne nessuna. Lo dice anche il Riferimento, nella sua voce: la chiamata costa poco, ma niente ne limita la frequenza. Prima di tutto, metti un interruttore davanti a ogni riga di debug che aggiungi, così puoi spegnere il rumore senza doverle andare a cercare di nuovo: [source,lua] ---- local DEBUG = true function debugLog(text) if DEBUG then debugPrint(text) end end ---- Ogni `debugLog` del gioco obbedisce a quell'unico `true`. Cambialo in `false` e il gioco tace; rimettilo com'era e torna tutto. Poi stampa quando qualcosa *succede*, invece che a ogni fotogramma: [source,lua] ---- debugLog("hit at y " .. math.floor(block.y) .. ", lives now " .. lives) ---- Oppure stampa a tempo, con un conto alla rovescia fatto esattamente come quello che fa comparire i blocchi: [source,lua] ---- reportTimer = reportTimer - 1 if reportTimer <= 0 then debugReport() reportTimer = REPORT_FRAMES end ---- La cartella `learn` ha tutto quanto in `08-when-it-goes-wrong.singe`: è il gioco della settima lezione con un resoconto circa ogni due secondi, una riga quando vieni colpito e una riga quando la partita finisce. Il secondo modo di vedere dentro è mettere le cose sullo schermo, che è meglio per tutto ciò che cambia a ogni fotogramma, perché sessanta righe al secondo in un terminale non servono a niente, ma un numero che scatta nell'angolo è facile da tenere d'occhio: [source,lua] ---- if DEBUG then overlayPrint(1, 3, "BLOCKS " .. #blocks .. " PLAYER " .. playerX) end ---- [#_cutting_the_problem_in_half] === Tagliare il problema a metà Quando non riesci a vedere quale di venti righe è sbagliata, smetti di guardare e comincia a togliere. Metti due trattini all'inizio di una riga e Lua ignora tutto il resto. Quello è un *commento*, e di solito serve a lasciare appunti per te stesso, ma l'uso migliore è questo: [source,lua] ---- -- overlayPrint(1, 1, "SCORE " .. score .. " LIVES " .. lives) ---- Commenta metà di `drawGame` ed eseguilo. Se il problema c'è ancora, sta nella metà che hai tenuto. Se è sparito, sta nella metà che hai tolto. Rimetti quella metà e taglia a metà *lei*. Venti righe richiedono cinque giri, e ogni giro è un salvataggio e un'occhiata. Funziona anche su cose più grandi delle righe. Commenta il corpo di `updatePlaying` e i blocchi smettono di muoversi, il che ti dice se la cosa che stai inseguendo sta nel movimento o nel disegno. Commenta il ramo di `collideRects` e guarda se il gioco smette di andare in crash. Mentre fai questo non stai cercando di aggiustare niente. Stai cercando di scoprire dove non è. [#_going_round_faster] === Fare il giro più in fretta Tutto questo dipende dal poter provare qualcosa in un paio di secondi, ed è a questo che serve `-R`. Lo usi dalla prima lezione; ecco che cosa fa davvero, e vale la pena saperlo ora che stai per appoggiarti a lui. Singe tiene d'occhio ogni file di script che il tuo gioco ha caricato. Quando ne salvi uno, il gioco riparte dall'inizio: tutto ciò che lo script aveva creato viene buttato via e il tuo file viene eseguito da capo, nel tempo che serve a leggere il file invece che in quello che serve ad avviare il motore. `F5` fa la stessa cosa a comando, senza salvare niente. Un tasto che tieni premuto durante un ricaricamento viene ignorato finché non lo lasci, così il giocatore non scappa via da solo dopo un ricaricamento. I due tipi di arresto qui si comportano in modo diverso, ed è utile sapere quale stai guardando senza leggere il messaggio. Uno script che non si compila *viene stampato, e Singe sopravvive*: la finestra resta aperta e vuota, il file resta sotto osservazione, e salvare la correzione riporta indietro il gioco. Un crash dentro una callback -- tutti i messaggi `attempt to` e le lamentele del motore sugli argomenti -- butta giù Singe, e lo riavvii a mano. [#_reading_a_traceback] === Leggere un traceback Le righe sotto `stack traceback:` sono l'elenco delle funzioni che erano in corso quando tutto si è fermato, a partire dalla più interna. Ecco di nuovo quello di `blocl`: ---- dodge/dodge.singe:60: in function 'spawnBlock' dodge/dodge.singe:92: in function 'updatePlaying' dodge/dodge.singe:136: in function 'onOverlayUpdate' ---- Leggilo dal basso verso l'alto ed è una frase. Singe ha chiamato `onOverlayUpdate`. Alla riga 136, `onOverlayUpdate` ha chiamato `updatePlaying`. Alla riga 92, `updatePlaying` ha chiamato `spawnBlock`. Alla riga 60, `spawnBlock` si è arresa. La riga in cima è dove si è rotto. Le righe sotto sono come ci è arrivato, e sono ciò che ti serve quando la riga in cima sembra innocente. Una funzione che somma un numero a `block.y` non è sbagliata; una funzione che ha messo un blocco nell'elenco senza una `y` lo è. Solo la pista collega le due cose, e la pista ti viene stampata ogni singola volta. [#_the_habit] === L'abitudine Tutto ciò che c'è qui sopra è tecnica. Questa è l'abitudine, e conta di più: *Cambia una cosa. Eseguilo. Guarda che cosa è successo.* Quando qualcosa non funziona, la tentazione è cambiare quattro cose insieme, perché una di loro sarà per forza quella giusta. Fallo e smette di funzionare in un modo nuovo, e ora non sai quale delle quattro è stata, o se due di loro si stanno annullando a vicenda. Hai reso il problema più grande e l'hai nascosto meglio. Una modifica. Esegui. Guarda. Sembra più lento. È la cosa più veloce che ci sia, e ogni programmatore esperto che incontrerai lo fa, esattamente per il motivo che stai per scoprire. [#_try_it_8] === Prova tu . *Conoscili tutti.* Fai apposta in `dodge.singe` ciascuno dei cinque errori di questa lezione, uno alla volta, e leggi il messaggio prima di correggerlo. Non ne avrai mai più paura. . *Osserva chi crea i blocchi.* Metti dentro `spawnBlock` un `debugPrint` che stampi la `x` del nuovo blocco. Gioca per trenta secondi, poi guarda i numeri. Sono sparsi su tutto lo schermo, o preferiscono un lato? . *Spegnilo.* Cambia `DEBUG` in `false` nello script della lezione, e assicurati che non venga stampato niente e che il gioco non cambi. . *Un bug senza errore.* In `updatePlaying`, cambia `if spawnTimer <= 0 then` in `if spawnTimer == 0 then`, che sembra voler dire la stessa cosa. Eseguilo e comincia una partita. Non c'è nessun errore, niente va in crash, e non cade mai nemmeno un blocco, quindi non puoi neanche perdere. Metti un `debugLog` dentro `spawnBlock`, aggiungi `spawnTimer` alla prima riga che stampa `debugReport` e gioca di nuovo. Scopri da quello che dicono, e da quello che uno dei due non dice mai, perché `==` era la domanda sbagliata da fare. . *Rompi il nome di una callback.* Rinomina `onInputPressed` in `onInputPress` ed eseguilo. Non viene stampato nessun errore, niente va in crash, il giocatore non si muove e la barra spaziatrice non fa partire una partita. Poi spiega a te stesso perché questo è l'errore più pericoloso di questa lezione. [#_what_you_learned_8] === Cosa hai imparato * Gli errori hanno un file, una riga e una lamentela. Leggili in quest'ordine. * `Error running script` vuol dire che non è stato eseguito niente; la forma con un traceback vuol dire che stava girando e si è fermato. * `nil` è un nome senza niente dietro, e sta dietro alla maggior parte dei messaggi di errore che vedrai. * La parola tra parentesi nomina la cosa vuota, e dice se era una globale, una locale, un campo o un upvalue. * Un `end` mancante viene segnalato alla fine del file, e le parentesi ti dicono dov'è davvero lo sbaglio. * Le lamentele del motore hanno un aspetto diverso: un numero di riga, il nome di una funzione e che cosa voleva invece. * `debugPrint` ti mostra che cosa contiene davvero una variabile. `tostring` rende stampabile qualsiasi cosa, e un interruttore come `DEBUG` spegne il rumore. * Commenta una metà con `--` per scoprire in quale metà sta il problema. * Una callback scritta male non produce nessun errore, il che la rende peggiore di una che lo produce. * Cambia una cosa, eseguilo, guarda. [#_next_time_8] === La prossima volta Questa è la fine della prima parte. Sai scrivere un gioco, e ora sai anche aggiustarne uno, il che vuol dire che tutto ciò che viene da qui in poi è aggiunta, non fondamenta. La seconda parte comincia sostituendo quei riquadri colorati con delle vere immagini, e da lì il gioco che stai costruendo comincia ad assomigliare a un gioco a cui qualcun altro vorrebbe giocare.