singe/docs/de/lessons/08-when-it-goes-wrong.adoc

482 lines
22 KiB
Text

[#_lesson_8_when_it_goes_wrong]
== Lektion 8: Wenn etwas schiefgeht
image::learn/08-when-it-goes-wrong.png[Die fertige Lektion, 480]
Du hast ein Spiel geschrieben. Ab jetzt wirst du mehr Zeit damit verbringen, Programme zu
reparieren, als sie zu schreiben, und das ist kein Zeichen, dass mit dir etwas nicht stimmt. Das
ist die Arbeit. Der Unterschied zwischen jemandem, dem Programmieren Spaß macht, und jemandem, der
es aufgibt, ist fast nie Talent; es ist die Frage, ob er gelernt hat zu lesen, was der Computer
ihm sagt.
In dieser Lektion gibt es also nichts Neues zu bauen. Es geht um die fünf Meldungen, die du für den
Rest deines Lebens am häufigsten sehen wirst, was jede davon wirklich bedeutet, und um vier Wege,
den Fehler dahinter aufzuspüren. Lass `dodge.singe` aus Lektion sieben offen, denn du wirst es
mehrmals absichtlich kaputt machen.
[#_two_kinds_of_stop]
=== Zwei Arten anzuhalten
Singe hält aus zwei verschiedenen Gründen an, und es sagt das auf zwei verschiedene Arten.
Wenn das Skript gar nicht erst startet, bekommst du diese Form:
----
Error running script: dodge/dodge.singe:92: 'then' expected near 'spawnBlock'
----
Lua liest deine ganze Datei, bevor es auch nur ein Wort davon ausführt, und das hier heißt, dass es
aus dem Gelesenen nicht schlau wurde. Überhaupt nichts ist gelaufen.
Wenn das Skript gut gestartet ist, aber später etwas schiefging, bekommst du stattdessen diese
Form:
----
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'
----
Das Spiel lief. Singe hat dein `onOverlayUpdate` aufgerufen, wie es das sechzigmal pro Sekunde tut,
und diesmal kam der Aufruf nicht zurück.
Beide Formen enthalten dieselben drei Dinge, und das sind genau die drei, die du willst: eine
Datei, eine Zeile und eine Beschwerde. Lies sie in dieser Reihenfolge, jedes Mal.
Die Zeilennummern in dieser Lektion stammen aus dem fertigen Listing von Lektion sieben. Liegen
deine um eine oder zwei Zeilen daneben, dann tut es deine Datei auch, und nichts am Lesen eines
Fehlers hängt davon ab, dass die Nummer stimmt.
[#_the_ones_that_stop_it_before_it_starts]
=== Die, die es aufhalten, bevor es anfängt
Das sind die leichten, und es sind auch die, die sich am schlimmsten anfühlen, weil der Bildschirm
schwarz bleibt und es nichts zu sehen gibt.
Geh in `updatePlaying` und lösch das Wort `then` aus der Zeile `if spawnTimer <= 0 then`.
Speichere:
----
Error running script: dodge/dodge.singe:92: 'then' expected near 'spawnBlock'
----
Lua ist ans Ende der `if`-Zeile gekommen, wollte das Wort `then` und hat stattdessen den Anfang
der nächsten Zeile gefunden -- das ist `spawnBlock()`, und daher kommt der Teil
`near 'spawnBlock'`. „Near“ heißt „hier habe ich es gemerkt“, und das ist meistens das Erste
*nach* dem, was du weggelassen hast.
Jetzt ein fieserer. Setz `then` wieder ein und lösch stattdessen das `end` in der letzten Zeile von
`spawnBlock`. Speichere:
----
Error running script: dodge/dodge.singe:148: 'end' expected (to close 'function' at line 58) near <eof>
----
Zeile 148 ist das Ende der Datei, gleich hinter ihrer letzten Zeile. An Zeile 148 ist nichts
falsch. Lua hat bis ganz zum
Ende gelesen -- `<eof>` nennt es das Ende einer Datei -- und dabei immer noch auf ein `end`
gewartet, das nie kam, und bis dahin hatte es jede Funktion nach `spawnBlock` verschluckt, als
gehöre sie dazu.
Die nützliche Hälfte dieser Meldung ist `(to close 'function' at line 58)`, und dort fängt
`spawnBlock` an. Wenn eine Meldung aufs Ende der Datei zeigt, glaub dem, was in den Klammern steht,
nicht der Zeilennummer. Das ist auch der Grund, warum die Einrückung so wichtig ist: Ein fehlendes
`end` ist in einer Reihe von Zeilen fast unsichtbar und sofort offensichtlich, sobald alles darunter
eine Stufe zu weit eingerückt ist.
Zwei weitere, die du wiedererkennen solltest, denn beiden begegnest du noch diese Woche:
----
Error running script: dodge/dodge.singe:41: unfinished string near '"GAME OVER)'
----
Ein Anführungszeichen, das du geöffnet und nie geschlossen hast; das kennst du aus Lektion eins.
----
Error running script: dodge/dodge.singe:135: 'then' expected near '='
----
Das ist `if state = "playing" then`. Ein `=` setzt eine Variable, zwei `==` stellen eine Frage, und
Lua lässt dich in einem `if` keine setzen. Es sieht nach einer seltsamen Beschwerde aus, und sie
bedeutet immer dasselbe.
[#_attempt_to_index_a_nil_value]
=== Attempt to Index a Nil Value
Diese Meldung wirst du mit großem Abstand am häufigsten sehen.
Geh in `spawnBlock` und schreib `block` in der ersten Zeile, die es benutzt, falsch, sodass dort
`blocl.x = math.random(0, screenWidth - BLOCK_SIZE)` steht. Speichere, drück die Leertaste:
----
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'
----
*Indizieren* ist das, was der Punkt tut. `blocl.x` heißt „geh in das Ding namens `blocl` und such
darin das `x`“. Das geht nur bei einer Tabelle.
*Nil* nennt Lua einen Namen, hinter dem nichts steht. Du hast es bisher selten gesehen, weil du
sorgfältig allem einen Wert gegeben hast, aber jeder Name, den du nie benutzt hast, ist schon
`nil`, und wer einen Namen falsch tippt, erfindet einen nagelneuen.
Die ganze Meldung sagt also: *Du hast einen Punkt auf etwas angewendet, das es nicht gibt*.
Der Teil in Klammern ist das Geschenk. `global 'blocl'` ist genau der Name des Dings, das leer
war. Such in deiner Datei danach. Kommt er genau einmal vor, ist es ein Tippfehler, und du hast ihn
in zehn Sekunden gefunden.
Das Wort vor dem Namen sagt dir, wo Lua danach gesucht hat:
* `global` -- ein Name, vor dem nirgends ein `local` steht.
* `local` -- ein Name, den du in der Funktion, in der du gerade bist, mit `local` deklariert hast.
* `field` -- ein Name nach einem Punkt, wie das `y` in `block.y`.
* `upvalue` -- ein `local` von weiter außen, das diese Funktion sehen kann, und das sind alle
Variablen von `dodge.singe`, denn sie werden oben in der Datei deklariert und in Funktionen
benutzt.
`field` ist die andere häufige Ursache, und die ist überhaupt kein Tippfehler. Wenn du
`block.hit.when` geschrieben und `block.hit` nie gesetzt hast, bekommst du
`attempt to index a nil value (field 'hit')`. Der Punkt, der scheitert, ist der zweite. Lua sagt
dir, welcher, indem es ihn beim Namen nennt.
[#_attempt_to_call_a_nil_value]
=== Attempt to Call a Nil Value
Mach `blocl` wieder rückgängig. Jetzt schreib eine Engine-Funktion falsch: Ändere in `drawGame`
das `overlayPrint` in `overlayPirnt`. Speichere:
----
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'
----
Dasselbe `nil`, anderes Vergehen. *Aufrufen* ist das, was die Klammern tun, und das geht nur bei
einer Funktion. Es gibt keine Funktion namens `overlayPirnt`, also ist `overlayPirnt` gleich
`nil`, also haben die Klammern nichts, was sie aufrufen könnten.
Das passiert aus vier Gründen, nach Häufigkeit geordnet:
. Du hast eine deiner eigenen Funktionen falsch geschrieben.
. Du hast eine von Singe falsch geschrieben. Nichts in der Engine prüft deine Rechtschreibung, wenn
die Datei geladen wird, also ist ein falsch geschriebener Engine-Name einfach ein weiterer Name,
der zufällig leer ist, und er kostet dich nichts, bis die Zeile läuft.
. Die Funktion gibt es in Singe überhaupt nicht -- es war eine vernünftige Vermutung, und
vernünftige Vermutungen liegen etwa die Hälfte der Zeit daneben. Die Funktionsliste in der
Referenz ist der einzige Weg, sicher zu sein.
. Du hast sie `local` gemacht und von irgendwo oberhalb der Stelle aufgerufen, an der sie
deklariert ist. Genau darum sind die Funktionen in `dodge.singe` global.
Es gibt eine viel schlimmere Version dieses Fehlers, und du bist ihr in Lektion eins begegnet, ohne
es zu wissen: Schreib einen *Callback* falsch, und du bekommst überhaupt keine Meldung.
`onOverlayUpdated` ist kein Fehler. Es ist eine völlig ordentliche Funktion, von der Singe nie
gehört hat und die es nie aufrufen wird, also bleibt der Bildschirm schwarz, und nichts beschwert
sich. Wenn ein ganzer Teil des Spiels überhaupt nichts tut, verdächtige den Namen.
[#_attempt_to_perform_arithmetic_on_a_nil_value]
=== Attempt to Perform Arithmetic on a Nil Value
Setz `overlayPrint` wieder ein. Schreib in `updatePlaying` in der Fall-Zeile `blockSpeed` falsch,
sodass sie `block.y = block.y + blockSped` lautet:
----
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'
----
Rechnen heißt `+`, `-`, `*` und `/`. Du kannst nicht nichts zu einer Zahl addieren. Wieder nennen
die Klammern das leere Ding, und wieder ist die häufigste Ursache ein Tippfehler -- aber dieser
hier hat noch eine zweite Ursache, die du kennen solltest, weil sie schwerer zu sehen ist.
Eine Variable, der du nie eine Zahl gegeben hast, ist `nil`, nicht null. Wenn du dem Spiel einen
Highscore hinzufügst und oben `local hiScore` ohne `= 0` schreibst, steht darin `nil`, bis etwas
sie setzt, und das erste `hiScore + score` hält das Spiel an. Lua lässt Zahlen nicht für dich bei
null anfangen. Das machst du.
Zwei nahe Verwandte, mit derselben Ursache und demselben Heilmittel:
----
dodge/dodge.singe:37: attempt to concatenate a nil value (upvalue 'hiScore')
----
`..`, das einen String an etwas hängt, das nicht da ist. Den siehst du jedes Mal, wenn du eine
Punktzahl ausgibst, die du zu setzen vergessen hast -- hier `hiScore`, angehängt an die
Punktezeile in `drawGame`, die jetzt Zeile 37 ist, weil `local hiScore` darüber steht.
----
dodge/dodge.singe:105: attempt to compare nil with number
----
Das ist `if score > hiScore then`, unter `state = "over"` gesetzt, um beim Ende eines Spiels die
beste Punktzahl festzuhalten, ohne dass etwas in `hiScore` steht. Beachte, dass diese Meldung nichts
in Klammern nennt. Wenn Lua dir nicht sagen kann, welche Seite leer war, zeigt dir der übernächste
Abschnitt, wie du es herausfindest.
[#_an_argument_of_the_wrong_kind]
=== Ein Argument der falschen Art
Jede Meldung bisher kam von Lua. Diese kommt von Singe, und sie sieht völlig anders aus, also lohnt
es sich, sie absichtlich kennenzulernen.
Angenommen, du willst sehen, was in `blocks` steckt, und versuchst, es auszugeben. Füg `drawGame`
diese Zeile hinzu, unter der Punktzahl:
[source,lua]
----
overlayPrint(1, 3, blocks)
----
----
37:overlayPrint: Argument 3 must be a string.
----
Kein Dateiname, kein Traceback, kein `attempt to` irgendwas. Eine Beschwerde der Engine fängt mit
der Zeilennummer an, dann kommt der Name der Funktion, die sich geweigert hat, dann, was sie
wollte. Hier hat `overlayPrint` eine Tabelle bekommen, wo es Text braucht, und es hat angehalten,
statt etwas Sinnloses zu zeichnen.
Die Schwestermeldung zählt, statt zu prüfen:
----
34:overlayBox: Expected 4 argument(s), got 3.
----
Das ist ein `overlayBox`, dem eine Ecke fehlt. Das seltsam aussehende `argument(s)` stammt von der
Engine, nicht aus einem Tippfehler von dir.
Wenn du so eine bekommst, ist der Eintrag zu dieser Funktion in der Referenz die Antwort, und das
kostet dich eine Minute. Jeder Eintrag listet die Argumente der Reihe nach auf, mit dem, was jedes
sein muss. Singe prüft sie, weil die Alternative ein Spiel ist, das zehn Minuten läuft und dann
Müll zeichnet.
[#_finding_out_what_is_really_in_there]
=== Herausfinden, was wirklich drinsteckt
Die Hälfte der Fehlersuche ist überhaupt nicht das Lesen von Fehlermeldungen. Es ist der Fall, in
dem nichts abstürzt und nichts funktioniert: Blöcke, die nie erscheinen, eine Spielfigur, die sich
nicht bewegen will, eine Punktzahl, die bei null hängt. Dafür musst du in das laufende Spiel
hineinsehen, und dafür gibt es zwei Wege.
Der erste ist `debugPrint`, das eine Zeile in das Terminal schreibt, aus dem du Singe gestartet
hast. Nimm die Zeile, aus der du nicht schlau wirst, und setz eins darüber:
[source,lua]
----
debugPrint("blocks " .. #blocks .. ", timer " .. spawnTimer)
----
Jetzt siehst du dir an, was wirklich da ist, statt dessen, wovon du sicher bist, dass es da ist,
und in dieser Lücke wohnen die Fehler. Gib `playerX` auf dieselbe Weise aus, und du siehst auch die
Wahrheit darüber: Es wird als `160.0` ausgegeben, nicht als `160`, weil Teilen mit `/` in Lua immer eine Zahl mit
Nachkommateil ergibt, auch wenn der Nachkommateil null ist.
Zwei Dinge werden dich beißen. Das erste ist, dass `debugPrint` auf Text oder einer Zahl besteht;
gib ihm etwas anderes, und es hält das Spiel an, so wie `overlayPrint` es getan hat.
`debugPrint(blocks)` zeigt dir keine Liste, und `debugPrint("left " .. goingLeft)` ganz oben in
`updatePlaying` zeigt dir auch kein `true` oder `false`:
----
dodge/dodge.singe:78: attempt to concatenate a boolean value (upvalue 'goingLeft')
----
`tostring` ist die Lösung. Es nimmt überhaupt alles und gibt dir Text dafür, also ist
`tostring(goingLeft)` entweder `"true"` oder `"false"`, und die Zeile funktioniert.
Das zweite ist, dass `onOverlayUpdate` sechzigmal pro Sekunde läuft, also gibt ein `debugPrint`
darin sechzig Zeilen pro Sekunde aus, und du kannst keine davon lesen. Die Referenz sagt das auch
in ihrem Eintrag: Der Aufruf ist billig, aber nichts bremst ihn.
Bevor du irgendetwas anderes machst, setz einen Schalter vor jede Debug-Zeile, die du hinzufügst,
damit du den Lärm abstellen kannst, ohne sie alle wieder aufspüren zu müssen:
[source,lua]
----
local DEBUG = true
function debugLog(text)
if DEBUG then
debugPrint(text)
end
end
----
Jedes `debugLog` im Spiel gehorcht diesem einen `true`. Ändere es in `false`, und das Spiel wird
still; ändere es zurück, und alles ist wieder da.
Dann gib etwas aus, wenn etwas *passiert*, statt in jedem Frame:
[source,lua]
----
debugLog("hit at y " .. math.floor(block.y) .. ", lives now " .. lives)
----
Oder gib es nach einem Timer aus, der genau so heruntergezählt wird, wie Blöcke erzeugt werden:
[source,lua]
----
reportTimer = reportTimer - 1
if reportTimer <= 0 then
debugReport()
reportTimer = REPORT_FRAMES
end
----
Im Ordner `learn` liegt das Ganze als `08-when-it-goes-wrong.singe`: Es ist das Spiel aus Lektion
sieben mit einem Bericht etwa alle zwei Sekunden, einer Zeile, wenn du getroffen wirst, und einer
Zeile, wenn das Spiel endet.
Der zweite Weg hineinzusehen ist, es auf den Bildschirm zu bringen, und das ist besser für alles,
was sich in jedem Frame ändert, denn sechzig Zeilen pro Sekunde in einem Terminal sind nutzlos,
aber eine Zahl, die in der Ecke tickt, lässt sich leicht beobachten:
[source,lua]
----
if DEBUG then
overlayPrint(1, 3, "BLOCKS " .. #blocks .. " PLAYER " .. playerX)
end
----
[#_cutting_the_problem_in_half]
=== Das Problem halbieren
Wenn du nicht siehst, welche von zwanzig Zeilen falsch ist, hör auf zu suchen und fang an
wegzunehmen.
Setz zwei Bindestriche an den Anfang einer Zeile, und Lua ignoriert den Rest davon. Das ist ein
*Kommentar*, und meistens hinterlässt man sich damit Notizen, aber die bessere Verwendung ist diese:
[source,lua]
----
-- overlayPrint(1, 1, "SCORE " .. score .. " LIVES " .. lives)
----
Kommentier die Hälfte von `drawGame` aus und starte es. Ist das Problem noch da, steckt es in der
Hälfte, die du behalten hast. Ist es weg, steckt es in der Hälfte, die du entfernt hast. Setz diese
Hälfte wieder ein und halbiere *sie*. Zwanzig Zeilen brauchen fünf solche Runden, und jede Runde
ist einmal speichern und einmal hinsehen.
Das funktioniert nicht nur mit Zeilen. Kommentier den Rumpf von `updatePlaying` aus, und die Blöcke
hören auf, sich zu bewegen, und das sagt dir, ob das, was du jagst, im Bewegen steckt oder im
Zeichnen. Kommentier den Zweig mit `collideRects` aus und sieh nach, ob das Spiel aufhört
abzustürzen. Du versuchst dabei nicht, irgendetwas zu reparieren. Du versuchst herauszufinden, wo
es nicht ist.
[#_going_round_faster]
=== Schnellere Runden
All das hängt davon ab, dass du etwas in ein paar Sekunden ausprobieren kannst, und dafür ist `-R`
da. Du benutzt es seit Lektion eins; hier steht, was es wirklich tut, und das solltest du jetzt
wissen, wo du dich darauf stützen wirst.
Singe beobachtet jede Skriptdatei, die dein Spiel geladen hat. Wenn du eine speicherst, fängt das
Spiel wieder von vorne an: Alles, was das Skript gemacht hat, wird weggeworfen, und deine Datei
läuft neu, in der Zeit, die das Lesen der Datei braucht, und nicht in der Zeit, die das Starten der
Engine braucht. `F5` macht dasselbe auf Wunsch, ohne dass du etwas speicherst.
Eine Taste, die du während des Neuladens gedrückt hältst, wird ignoriert, bis du sie loslässt, also
läuft die Spielfigur nach einem Neuladen nicht von allein davon.
Die beiden Arten anzuhalten verhalten sich hier verschieden, und es ist nützlich zu wissen, welche
du vor dir hast, ohne die Meldung zu lesen. Ein Skript, das sich nicht kompilieren lässt, wird
*gemeldet und überstanden*: Das Fenster bleibt offen und leer, die Datei wird weiter beobachtet,
und wenn du die Korrektur speicherst, kommt das Spiel zurück. Ein Absturz in einem Callback -- alle
`attempt to`-Meldungen und die Beschwerden der Engine über Argumente -- reißt Singe mit, und du
startest es von Hand neu.
[#_reading_a_traceback]
=== Einen Traceback lesen
Die Zeilen unter `stack traceback:` sind die Liste der Funktionen, die gerade liefen, als alles
anhielt, die innerste zuerst. Hier ist noch einmal der von `blocl`:
----
dodge/dodge.singe:60: in function 'spawnBlock'
dodge/dodge.singe:92: in function 'updatePlaying'
dodge/dodge.singe:136: in function 'onOverlayUpdate'
----
Lies ihn von unten nach oben, und er ist ein Satz. Singe hat `onOverlayUpdate` aufgerufen. In
Zeile 136 hat `onOverlayUpdate` `updatePlaying` aufgerufen. In Zeile 92 hat `updatePlaying`
`spawnBlock` aufgerufen. In Zeile 60 hat `spawnBlock` aufgegeben.
Die oberste Zeile ist die Stelle, an der es kaputtging. Die Zeilen darunter zeigen, wie es dorthin
kam, und die brauchst du, wenn die oberste Zeile unschuldig aussieht. Eine Funktion, die eine Zahl
zu `block.y` addiert, ist nicht falsch; eine Funktion, die einen Block ohne `y` in die Liste gelegt
hat, schon. Nur die Spur verbindet die beiden, und die Spur wird dir jedes einzelne Mal
ausgegeben.
[#_the_habit]
=== Die Gewohnheit
Alles oben ist Technik. Das hier ist die Gewohnheit, und sie ist wichtiger:
*Ändere eine Sache. Starte es. Sieh dir an, was passiert ist.*
Wenn etwas nicht funktionieren will, ist die Versuchung groß, vier Dinge auf einmal zu ändern, weil
eines davon es ja sein muss. Mach das, und es funktioniert auf eine neue Art nicht, und jetzt weißt
du nicht, welches der vier es war oder ob sich zwei davon gegenseitig aufheben. Du hast das Problem
größer gemacht und besser versteckt.
Eine Änderung. Starten. Hinsehen. Es fühlt sich langsamer an. Es ist das Schnellste, was es gibt,
und jeder erfahrene Programmierer, dem du je begegnest, macht es so, aus genau dem Grund, den du
gleich herausfinden wirst.
[#_try_it_8]
=== Probier es aus
. *Lern sie alle kennen.* Mach jeden der fünf Fehler aus dieser Lektion absichtlich in
`dodge.singe`, einen nach dem anderen, und lies die Meldung, bevor du ihn behebst. Du wirst nie
wieder Angst vor ihnen haben.
. *Beobachte den Erzeuger.* Setz ein `debugPrint` in `spawnBlock`, das das `x` des neuen Blocks
ausgibt. Spiel dreißig Sekunden lang, dann sieh dir die Zahlen an. Sind sie über den Bildschirm
verteilt, oder bevorzugen sie eine Seite?
. *Schalt es ab.* Ändere im Skript der Lektion `DEBUG` in `false` und überzeug dich davon, dass
nichts ausgegeben wird und das Spiel unverändert ist.
. *Ein Fehler ohne Fehlermeldung.* Ändere in `updatePlaying` `if spawnTimer <= 0 then` in
`if spawnTimer == 0 then`, was so aussieht, als bedeute es dasselbe. Starte es und fang ein Spiel
an. Es gibt keine Fehlermeldung, nichts stürzt ab, und kein einziger Block fällt je, du kannst
also nicht einmal verlieren. Setz ein `debugLog` in `spawnBlock`, füg `spawnTimer` der ersten
Zeile hinzu, die `debugReport` ausgibt, und spiel noch einmal. Finde aus dem, was sie sagen, und
aus dem, was eines von beiden nie sagt, heraus, warum `==` die falsche Frage war.
. *Mach den Namen eines Callbacks kaputt.* Benenne `onInputPressed` in `onInputPress` um und starte
es. Keine Fehlermeldung wird ausgegeben, nichts stürzt ab, die Spielfigur bewegt sich nicht, und die Leertaste
startet kein Spiel. Dann erklär dir selbst, warum das der gefährlichste Fehler in dieser Lektion
ist.
[#_what_you_learned_8]
=== Was du gelernt hast
* Fehlermeldungen haben eine Datei, eine Zeile und eine Beschwerde. Lies sie in dieser Reihenfolge.
* `Error running script` heißt, dass nichts gelaufen ist; die Form mit einem Traceback heißt, dass
es lief und angehalten hat.
* `nil` ist ein Name, hinter dem nichts steht, und es steckt hinter den meisten Fehlermeldungen,
die du je sehen wirst.
* Das Wort in Klammern nennt das leere Ding und sagt, ob es global, lokal, ein Feld oder ein
Upvalue war.
* Ein fehlendes `end` wird am Ende der Datei gemeldet, und die Klammern sagen dir, wo der Fehler
wirklich steckt.
* Beschwerden der Engine sehen anders aus: eine Zeilennummer, ein Funktionsname und was sie
stattdessen wollte.
* `debugPrint` zeigt dir, was wirklich in einer Variablen steckt. `tostring` macht alles
ausgebbar, und ein Schalter wie `DEBUG` stellt den Lärm ab.
* Kommentier mit `--` eine Hälfte aus, um herauszufinden, in welcher Hälfte das Problem steckt.
* Ein falsch geschriebener Callback erzeugt überhaupt keine Fehlermeldung, und das macht ihn
schlimmer als einen Fehler, der eine erzeugt.
* Ändere eine Sache, starte es, sieh hin.
[#_next_time_8]
=== Beim nächsten Mal
Damit endet Teil eins. Du kannst ein Spiel schreiben, und jetzt kannst du auch eins reparieren,
und das heißt, dass alles ab hier Anbau ist und kein Fundament mehr. Teil zwei fängt damit an,
diese farbigen Kästen durch echte Bilder zu ersetzen, und von da an sieht das Spiel, das du baust,
allmählich wie ein Spiel aus, das auch jemand anders spielen wollen würde.