[#_lesson_8_when_it_goes_wrong] == Lección 8: Cuando algo sale mal image::learn/08-when-it-goes-wrong.png[La lección terminada, 480] Has escrito un juego. De aquí en adelante pasarás más tiempo arreglando programas que escribiéndolos, y eso no es señal de que te pase nada malo. Es el trabajo. La diferencia entre alguien que disfruta programando y alguien que lo deja casi nunca es el talento; es si aprendió a leer lo que le dice el ordenador. Así que en esta lección no hay nada nuevo que construir. Son los cinco mensajes que más verás durante el resto de tu vida, lo que significa de verdad cada uno, y cuatro formas de dar caza a la equivocación que hay detrás. Ten abierto `dodge.singe` de la lección siete, porque vas a romperlo a propósito varias veces. [#_two_kinds_of_stop] === Dos tipos de parada Singe se para por dos razones distintas, y lo dice de dos formas distintas. Cuando el script ni siquiera llega a arrancar, te sale algo con esta forma: ---- Error running script: dodge/dodge.singe:92: 'then' expected near 'spawnBlock' ---- Lua lee el archivo entero antes de ejecutar ni una palabra, y esto significa que no ha podido entender lo que ha leído. No se ha ejecutado nada en absoluto. Cuando el script arrancó bien pero algo salió mal después, te sale en cambio esta 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' ---- El juego estaba funcionando. Singe llamó a tu `onOverlayUpdate`, como hace sesenta veces por segundo, y esta vez la llamada no volvió. Las dos formas contienen las mismas tres cosas, y son las tres cosas que quieres: un archivo, una línea y una queja. Léelas en ese orden, siempre. Los números de línea de esta lección son los del listado terminado de la lección siete. Si los tuyos están desviados una o dos líneas, también lo está tu archivo, y nada de lo que hace falta para leer un error depende de que el número coincida. [#_the_ones_that_stop_it_before_it_starts] === Los que lo paran antes de empezar Estos son los fáciles, y también son los que peor sientan, porque la pantalla se queda negra y no hay nada que mirar. Ve a `updatePlaying` y borra la palabra `then` de la línea `if spawnTimer <= 0 then`. Guarda: ---- Error running script: dodge/dodge.singe:92: 'then' expected near 'spawnBlock' ---- Lua llegó al final de la línea del `if`, quería la palabra `then` y en su lugar encontró el principio de la línea siguiente -- que es `spawnBlock()`, y esa es la parte `near 'spawnBlock'`. «Near» significa «aquí es donde me he dado cuenta», y suele ser lo primero que va *después* de lo que te has dejado. Ahora uno más feo. Vuelve a poner `then`, y en su lugar borra el `end` de la última línea de `spawnBlock`. Guarda: ---- Error running script: dodge/dodge.singe:148: 'end' expected (to close 'function' at line 58) near ---- La línea 148 es el final del archivo, justo después de su última línea. A la línea 148 no le pasa nada. Lua leyó hasta el final del todo -- `` es como llama al final de un archivo -- esperando todavía un `end` que nunca llegó, y para entonces se había tragado todas las funciones que van después de `spawnBlock` como si fueran parte de ella. La mitad útil de ese mensaje es `(to close 'function' at line 58)`, que es donde empieza `spawnBlock`. Cuando un mensaje señala el final del archivo, créete lo que dice entre paréntesis, no el número de línea. Por esto también importa tanto la sangría: un `end` que falta es casi invisible en una lista de líneas, y salta a la vista en cuanto todo lo que hay debajo está sangrado un paso de más. Dos más que vale la pena reconocer, porque te vas a encontrar los dos esta misma semana: ---- Error running script: dodge/dodge.singe:41: unfinished string near '"GAME OVER)' ---- Unas comillas que abriste y nunca cerraste, que ya viste en la lección uno. ---- Error running script: dodge/dodge.singe:135: 'then' expected near '=' ---- Ese es `if state = "playing" then`. Un `=` da valor a una variable, dos `==` hacen una pregunta, y Lua no te deja dar valor a una dentro de un `if`. Parece una queja rara, y siempre significa lo mismo. [#_attempt_to_index_a_nil_value] === Attempt to index a nil value Este es el que más verás, con mucha diferencia. Ve a `spawnBlock` y escribe mal `block` en la primera línea que lo usa, para que quede `blocl.x = math.random(0, screenWidth - BLOCK_SIZE)`. Guarda y pulsa la barra espaciadora: ---- 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' ---- *Indexar* es lo que hace el punto. `blocl.x` significa «entra en la cosa llamada `blocl` y busca la `x` que tiene dentro». Eso solo se puede hacer con una tabla. *Nil* es como llama Lua a un nombre que no tiene nada detrás. No lo has visto mucho porque has tenido cuidado de darle un valor a todo, pero cualquier nombre que no hayas usado nunca ya es `nil`, y escribir mal un nombre se inventa uno completamente nuevo. Así que el mensaje entero dice: *has usado un punto en algo que no existe*. La parte entre paréntesis es el regalo. `global 'blocl'` es el nombre exacto de la cosa que estaba vacía. Búscalo en tu archivo. Si aparece exactamente una vez, es una falta de ortografía, y la has encontrado en diez segundos. La palabra que va antes del nombre te dice dónde lo buscó Lua: * `global` -- un nombre que no tiene `local` delante en ningún sitio. * `local` -- un nombre que declaraste con `local` en la función en la que estás. * `field` -- un nombre detrás de un punto, como la `y` de `block.y`. * `upvalue` -- un `local` de más afuera que esta función puede ver, que es lo que son todas las variables de `dodge.singe`, ya que se declaran al principio del archivo y se usan dentro de funciones. `field` es la otra causa habitual, y no es ninguna falta de ortografía. Si escribiste `block.hit.when` y nunca diste valor a `block.hit`, te sale `attempt to index a nil value (field 'hit')`. El punto que falla es el segundo. Lua te dice cuál nombrándolo. [#_attempt_to_call_a_nil_value] === Attempt to call a nil value Arregla `blocl`. Ahora escribe mal una función del motor: en `drawGame`, cambia `overlayPrint` por `overlayPirnt`. Guarda: ---- 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' ---- El mismo `nil`, otro delito. *Llamar* es lo que hacen los paréntesis, y eso solo se puede hacer con una función. No hay ninguna función llamada `overlayPirnt`, así que `overlayPirnt` es `nil`, así que los paréntesis no tienen nada a lo que llamar. Esto pasa por cuatro razones, de más a menos frecuente: . Has escrito mal una de tus propias funciones. . Has escrito mal una de las de Singe. Nada en el motor comprueba cómo escribes los nombres cuando se carga el archivo, así que un nombre del motor mal escrito es solo otro nombre que resulta estar vacío, y no te cuesta nada hasta que se ejecuta la línea. . La función no existe en Singe en absoluto -- era una suposición razonable, y las suposiciones razonables fallan más o menos la mitad de las veces. La lista de funciones de la referencia es la única forma de estar seguro. . La hiciste `local` y la llamaste desde algún sitio por encima de donde se declara. Esa es justo la razón de que las funciones de `dodge.singe` sean globales. Hay una versión mucho peor de esta equivocación, y te la encontraste en la lección uno sin saberlo: escribe mal un *callback* y no te sale ningún mensaje. `onOverlayUpdated` no es un error. Es una función perfectamente válida de la que Singe no ha oído hablar nunca y a la que nunca llamará, así que la pantalla se queda negra y nada se queja. Cuando toda una parte del juego no hace absolutamente nada, sospecha del nombre. [#_attempt_to_perform_arithmetic_on_a_nil_value] === Attempt to perform arithmetic on a nil value Vuelve a poner `overlayPrint`. En `updatePlaying`, escribe mal `blockSpeed` en la línea de caída, para que quede `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' ---- La aritmética es `+`, `-`, `*` y `/`. No se puede sumar la nada a un número. De nuevo los paréntesis nombran la cosa vacía, y de nuevo la causa más común es una errata -- pero este tiene una segunda causa que vale la pena conocer, porque es más difícil de ver. Una variable a la que nunca diste un número es `nil`, no cero. Si añades un récord al juego y escribes `local hiScore` al principio sin `= 0`, guarda `nil` hasta que algo le dé valor, y el primer `hiScore + score` detiene el juego. Lua no empieza los números en cero por ti. Los empiezas tú en cero. Dos parientes cercanos, con la misma causa y la misma cura: ---- dodge/dodge.singe:37: attempt to concatenate a nil value (upvalue 'hiScore') ---- `..` uniendo una cadena a algo que no está ahí. Verás este cada vez que imprimas una puntuación a la que se te olvidó dar valor -- aquí, `hiScore` añadido a la línea de la puntuación en `drawGame`, que es la línea 37 ahora que `local hiScore` está encima. ---- dodge/dodge.singe:105: attempt to compare nil with number ---- Ese es `if score > hiScore then`, puesto debajo de `state = "over"` para guardar la mejor puntuación cuando acaba una partida, sin nada en `hiScore`. Fíjate en que este mensaje no nombra nada entre paréntesis. Cuando Lua no puede decirte qué lado estaba vacío, la sección que va después de la siguiente te enseña a averiguarlo. [#_an_argument_of_the_wrong_kind] === Un argumento del tipo equivocado Todos los mensajes hasta ahora venían de Lua. Este viene de Singe, y tiene un aspecto completamente distinto, así que vale la pena conocerlo a propósito. Supón que quieres ver qué hay en `blocks`, e intentas imprimirlo. Añade esta línea a `drawGame`, debajo de la puntuación: [source,lua] ---- overlayPrint(1, 3, blocks) ---- ---- 37:overlayPrint: Argument 3 must be a string. ---- Ni nombre de archivo, ni traza, ni ningún `attempt to` nada. Una queja del motor empieza con el número de línea, luego el nombre de la función que se ha negado y luego lo que quería. Aquí a `overlayPrint` le han dado una tabla donde necesita texto, y se ha parado en lugar de dibujar algo sin sentido. El mensaje hermano cuenta en lugar de comprobar: ---- 34:overlayBox: Expected 4 argument(s), got 3. ---- Eso es un `overlayBox` al que le falta una esquina. Ese `argument(s)` tan raro es del motor, no una errata tuya. Cuando te salga uno de estos, la entrada de esa función en la referencia es la respuesta, y te llevará un minuto. Cada entrada enumera los argumentos en orden, con lo que tiene que ser cada uno. Singe los comprueba porque la alternativa es un juego que funciona durante diez minutos y luego dibuja basura. [#_finding_out_what_is_really_in_there] === Averiguar qué hay de verdad ahí dentro La mitad de depurar no consiste en leer errores. Es el caso en el que nada se cae y nada funciona: bloques que nunca aparecen, un jugador que no se mueve, una puntuación atascada en cero. Para eso necesitas ver el interior del juego mientras se ejecuta, y hay dos formas. La primera es `debugPrint`, que escribe una línea en la terminal desde la que arrancaste Singe. Coge la línea que no consigues entender y pon una encima: [source,lua] ---- debugPrint("blocks " .. #blocks .. ", timer " .. spawnTimer) ---- Ahora estás mirando lo que hay de verdad en lugar de lo que estás seguro de que hay, y en ese hueco es donde viven los fallos. Imprime `playerX` del mismo modo y también verás la verdad sobre él: se imprime como `160.0`, no `160`, porque dividir con `/` en Lua siempre da un número con parte decimal, aunque esa parte sea cero. Hay dos cosas que te van a morder. La primera es que `debugPrint` exige texto o un número; dale cualquier otra cosa y detiene el juego igual que hizo `overlayPrint`. `debugPrint(blocks)` no te enseñará una lista, y `debugPrint("left " .. goingLeft)` al principio de `updatePlaying` tampoco te enseñará un `true` ni un `false`: ---- dodge/dodge.singe:78: attempt to concatenate a boolean value (upvalue 'goingLeft') ---- `tostring` es el arreglo. Acepta cualquier cosa y te da texto para ella, así que `tostring(goingLeft)` es `"true"` o `"false"` y la línea funciona. La segunda es que `onOverlayUpdate` se ejecuta sesenta veces por segundo, así que un `debugPrint` dentro de él imprime sesenta líneas por segundo y no puedes leer ninguna. La referencia lo dice en su entrada: la llamada es barata, pero nada la frena. Antes que nada, pon un interruptor delante de cada línea de depuración que añadas, para poder quitar el ruido sin tener que volver a buscarlas todas: [source,lua] ---- local DEBUG = true function debugLog(text) if DEBUG then debugPrint(text) end end ---- Cada `debugLog` del juego obedece a ese único `true`. Cámbialo a `false` y el juego se queda callado; vuelve a cambiarlo y todo regresa. Luego imprime cuando algo *pasa*, en lugar de en cada fotograma: [source,lua] ---- debugLog("hit at y " .. math.floor(block.y) .. ", lives now " .. lives) ---- O imprime con un temporizador, que cuenta hacia atrás exactamente igual que cuando nacen los bloques: [source,lua] ---- reportTimer = reportTimer - 1 if reportTimer <= 0 then debugReport() reportTimer = REPORT_FRAMES end ---- La carpeta `learn` tiene todo esto como `08-when-it-goes-wrong.singe`: es el juego de la lección siete con un informe cada dos segundos más o menos, una línea cuando te dan y una línea cuando termina la partida. La segunda forma de ver el interior es ponerlo en pantalla, que es mejor para cualquier cosa que cambie en cada fotograma, porque sesenta líneas por segundo en una terminal no sirven de nada, pero un número que va cambiando en una esquina es fácil de vigilar: [source,lua] ---- if DEBUG then overlayPrint(1, 3, "BLOCKS " .. #blocks .. " PLAYER " .. playerX) end ---- [#_cutting_the_problem_in_half] === Partir el problema por la mitad Cuando no ves cuál de veinte líneas está mal, deja de mirar y empieza a quitar. Pon dos guiones al principio de una línea y Lua ignora el resto. Eso es un *comentario*, y lo normal es usarlo para dejarte notas, pero el mejor uso es este: [source,lua] ---- -- overlayPrint(1, 1, "SCORE " .. score .. " LIVES " .. lives) ---- Comenta la mitad de `drawGame` y ejecútalo. Si el problema sigue ahí, está en la mitad que has dejado. Si ha desaparecido, está en la mitad que has quitado. Vuelve a poner esa mitad y pártela *a ella* por la mitad. Veinte líneas son cinco rondas de esto, y cada ronda es guardar una vez y mirar una vez. Funciona con algo más que líneas. Comenta el cuerpo de `updatePlaying` y los bloques dejan de moverse, lo que te dice si lo que persigues está en el movimiento o en el dibujo. Comenta la rama de `collideRects` y mira si el juego deja de caerse. Mientras haces esto no intentas arreglar nada. Intentas averiguar dónde no está. [#_going_round_faster] === Dar vueltas más deprisa Todo esto depende de poder probar algo en un par de segundos, y para eso está `-R`. Lo usas desde la lección uno; esto es lo que hace de verdad, y vale la pena saberlo ahora que vas a apoyarte en él. Singe vigila cada archivo de script que ha cargado tu juego. Cuando guardas uno, el juego vuelve a empezar desde el principio: todo lo que había creado el script se tira y tu archivo se ejecuta de nuevo, en lo que se tarda en leer el archivo y no en lo que se tarda en arrancar el motor. `F5` hace lo mismo cuando quieras, sin guardar nada. Una tecla que mantienes pulsada durante una recarga se ignora hasta que la sueltas, así que el jugador no se irá corriendo él solo después de una recarga. Los dos tipos de parada se comportan de forma distinta aquí, y es útil saber cuál estás viendo sin leer el mensaje. Un script que no compila *se imprime y Singe sigue vivo*: la ventana sigue abierta y vacía, el archivo sigue vigilado, y guardar el arreglo devuelve el juego. Un fallo dentro de un callback -- todos los mensajes `attempt to` y las quejas del motor sobre los argumentos -- tumba a Singe, y lo vuelves a arrancar a mano. [#_reading_a_traceback] === Leer una traza Las líneas que hay debajo de `stack traceback:` son la lista de funciones que estaban en marcha cuando todo se paró, empezando por la más interna. Aquí está otra vez la de `blocl`: ---- dodge/dodge.singe:60: in function 'spawnBlock' dodge/dodge.singe:92: in function 'updatePlaying' dodge/dodge.singe:136: in function 'onOverlayUpdate' ---- Léela de abajo arriba y es una frase. Singe llamó a `onOverlayUpdate`. En la línea 136, `onOverlayUpdate` llamó a `updatePlaying`. En la línea 92, `updatePlaying` llamó a `spawnBlock`. En la línea 60, `spawnBlock` se rindió. La línea de arriba es donde se rompió. Las líneas de debajo son cómo llegó ahí, y son lo que necesitas cuando la línea de arriba parece inocente. Una función que suma un número a `block.y` no está mal; una función que metió un bloque en la lista sin una `y` sí lo está. Solo el rastro une las dos, y el rastro se te imprime todas y cada una de las veces. [#_the_habit] === La costumbre Todo lo de arriba es técnica. Esto es la costumbre, y importa más: *Cambia una cosa. Ejecútalo. Mira lo que ha pasado.* Cuando algo no funciona, la tentación es cambiar cuatro cosas a la vez, porque seguro que una de ellas es la buena. Hazlo y dejará de funcionar de una forma nueva, y ahora no sabrás cuál de las cuatro ha sido, ni si dos de ellas se están anulando entre sí. Has hecho el problema más grande y lo has escondido mejor. Un cambio. Ejecutar. Mirar. Parece más lento. Es lo más rápido que hay, y todos los programadores con experiencia que conozcas lo hacen, exactamente por la razón que estás a punto de descubrir. [#_try_it_8] === Pruébalo . *Conócelos a todos.* Comete a propósito en `dodge.singe` cada una de las cinco equivocaciones de esta lección, de una en una, y lee el mensaje antes de arreglarla. Nunca más te darán miedo. . *Vigila dónde nacen.* Pon dentro de `spawnBlock` un `debugPrint` que imprima la `x` del bloque nuevo. Juega treinta segundos y luego mira los números. ¿Están repartidos por toda la pantalla, o prefieren un extremo? . *Apágalo.* Cambia `DEBUG` a `false` en el script de la lección, y comprueba por ti mismo que no se imprime nada y que el juego no ha cambiado. . *Un fallo sin error.* En `updatePlaying`, cambia `if spawnTimer <= 0 then` por `if spawnTimer == 0 then`, que parece querer decir lo mismo. Ejecútalo y empieza una partida. No hay ningún error, nada se cae y no cae ni un solo bloque, así que ni siquiera puedes perder. Pon un `debugLog` dentro de `spawnBlock`, añade `spawnTimer` a la primera línea que imprime `debugReport` y vuelve a jugar. Averigua por lo que dicen, y por lo que uno de ellos no dice nunca, por qué `==` era la pregunta equivocada. . *Rompe el nombre de un callback.* Cambia el nombre de `onInputPressed` por `onInputPress` y ejecútalo. No se imprime ningún error, nada se cae, el jugador no se mueve y la barra espaciadora no empieza una partida. Luego explícate a ti mismo por qué esa es la equivocación más peligrosa de esta lección. [#_what_you_learned_8] === Lo que has aprendido * Los errores tienen un archivo, una línea y una queja. Léelos en ese orden. * `Error running script` significa que no se ha ejecutado nada; la forma con traza significa que estaba funcionando y se paró. * `nil` es un nombre sin nada detrás, y está detrás de la mayoría de los mensajes de error que verás en tu vida. * La palabra entre paréntesis nombra la cosa vacía, y dice si era un global, un local, un campo o un upvalue. * Un `end` que falta se señala al final del archivo, y los paréntesis te dicen dónde está de verdad la equivocación. * Las quejas del motor tienen otro aspecto: un número de línea, un nombre de función y lo que quería en su lugar. * `debugPrint` te enseña lo que guarda de verdad una variable. `tostring` hace que cualquier cosa se pueda imprimir, y un interruptor como `DEBUG` quita el ruido. * Comenta una mitad con `--` para averiguar en qué mitad está el problema. * Un callback mal escrito no produce ningún error, y eso lo hace peor que uno que sí lo produce. * Cambia una cosa, ejecútalo, mira. [#_next_time_8] === La próxima vez Ese es el final de la primera parte. Sabes escribir un juego, y ahora sabes arreglarlo, lo que significa que todo lo que viene a partir de aquí es añadir y no poner cimientos. La segunda parte empieza cambiando esas cajas de colores por imágenes de verdad, y a partir de ahí el juego que has estado construyendo empieza a parecer un juego al que otra persona querría jugar.