Vom Fehlerbild zur Ursache, ohne Rateschleife
Jede Runde aus console.log und Neuladen beantwortet genau eine Frage von vorhin. Ein bedingter Haltepunkt an derselben Stelle beantwortet sie einmal und vollständig.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Die Konsolenausgabe beantwortet immer nur die Frage von vorhin
Der übliche Ablauf: Es gibt einen Fehler, du setzt eine Ausgabe an die verdächtige Stelle, lädst neu, siehst einen Wert, verstehst ihn nicht, setzt die nächste Ausgabe. Jede Runde kostet eine Minute und liefert genau einen Wert aus genau einem Durchlauf. Bei einem Fehler, der nur beim 417. Eintrag auftritt, ist das keine Suche mehr, sondern eine Zufallsstichprobe.
Der zweite Zeitfresser ist das Springen zwischen Vermutungen. Ohne festes Vorgehen wird die Stelle geprüft, die am leichtesten zu prüfen ist, und nicht die, an der der Wert zum ersten Mal falsch war. Bei Fehlern, die über mehrere Schichten wandern, führt das dazu, dass am Symptom repariert wird: Ein Standardwert deckt das undefined zu, und der eigentliche Fehler wandert eine Ebene tiefer.
Am teuersten sind die Fälle, die nur bei anderen auftreten. Ohne Source Maps im Build zeigt der Stack aus dem Betrieb auf Zeile 1 einer minimierten Datei und auf Funktionsnamen wie t und Ae. Ohne diese Zuordnung ist ein gemeldeter Fehler kaum mehr als der Hinweis, dass irgendwo etwas nicht stimmt, und die Suche beginnt bei null.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Das Vorgehen in sieben Schritten
Die Reihenfolge ist wichtiger als jedes einzelne Werkzeug. Wer sie einhält, kommt auch bei Fehlern voran, die selten auftreten oder sich auf dem eigenen Rechner nicht zeigen.
- 1
Das Fehlerbild festhalten
Notier die vollständige Meldung mit Aufrufstapel, die Schritte davor, Browser und Version, die Datenlage und die Frage, ob es nur im Betrieb auftritt. Vermeide dabei jede Ursachenvermutung, sie lenkt die Suche in eine Richtung, für die es noch keinen Grund gibt.
Geschafft, wenn: ein Satz, der Auslöser, Erwartung und Beobachtung enthält, ohne eine Deutung
- 2
Reproduzieren und einkochen
Löse den Fehler bewusst aus, dann entferne alles, was ihn nicht braucht: andere Komponenten, Erweiterungen im Browser, halbe Datensätze. Wenn er erst nach einem bestimmten Stand auftrat, engt git bisect den Zeitraum in wenigen Schritten auf einen einzelnen Stand ein.
Geschafft, wenn: eine Abfolge, die den Fehler zuverlässig auslöst, notfalls in einer leeren Seite mit zwanzig Zeilen
- 3
Den ersten eigenen Rahmen im Stapel finden
Prüf zuerst, ob die Source Maps greifen, also ob im Sources-Bereich dein Quelltext steht und nicht die minimierte Fassung. Setz danach Bibliotheken über die Ignore-Liste stumm, damit der Stapel nicht mit fremden Rahmen beginnt.
Geschafft, wenn: eine Datei und eine Zeile aus deinem Code statt eines Rahmens aus dem Framework
- 4
Anhalten statt ausgeben
Setz den Haltepunkt an diese Zeile und gib ihm eine Bedingung, die nur im Fehlerfall zutrifft. Wenn du den Ort noch nicht kennst, hilft das Anhalten bei Ausnahmen, wahlweise auch bei abgefangenen, oder ein Haltepunkt auf eine Netzwerkanfrage, auf eine DOM-Änderung oder auf einen bestimmten Ereignistyp.
Geschafft, wenn: der Debugger hält im Fehlerfall an und nicht in den harmlosen Durchläufen davor
- 5
Den Zustand vergleichen, nicht deuten
Lies im Bereich Scope die tatsächlichen Werte, nicht die erwarteten. Dann klick dich im Aufrufstapel nach oben durch die aufrufenden Rahmen und prüf denselben Wert dort. Für wiederkehrende Ausdrücke sind Live Expressions praktisch, weil sie ohne erneutes Anhalten aktuell bleiben.
Geschafft, wenn: eine benannte Abweichung: hier ist der Wert bereits falsch, im Rahmen darüber war er noch richtig
- 6
Netzwerk und Zeitpunkt prüfen
Sieh im Network-Bereich nach, was tatsächlich über die Leitung kam, und zwar unter Response und nicht unter Preview, denn Preview zeigt eine aufbereitete Ansicht. Prüf Status, Header und Reihenfolge. Mit Drosselung fallen Fehler auf, die auf einer schnellen Verbindung nie auftreten, etwa zwei Anfragen, deren Antworten in umgekehrter Reihenfolge eintreffen.
Geschafft, wenn: Klarheit darüber, ob die falschen Daten schon vom Server kamen oder erst im Code entstanden sind
- 7
Die Ursache absichern
Schreib zuerst den Test, der den Fehler nachstellt, und erst danach die Korrektur. Wenn der Test ohne die Änderung nicht fehlschlägt, hast du eine andere Stelle getroffen als gedacht. Notier im Änderungsverlauf die Ursache und nicht die Auswirkung.
Geschafft, wenn: ein Test, der ohne die Korrektur rot wird, und ein Eintrag, der die Ursache benennt
Sechs Schritte vom Symptom zur belegten Ursache
- 01 Halte fest, was passiert, ohne dabei schon eine Ursache zu nennen.
- 02 Koch den Fall ein, bis er in wenigen Zeilen zuverlässig auftritt.
- 03 Such im Stapel den ersten Rahmen, der aus deinem eigenen Code stammt.
- 04 Halte dort an und lies den Zustand, statt ihn aus Ausgaben zu erraten.
- 05 Geh im Aufrufstapel zurück, bis der Wert das letzte Mal richtig war.
- 06 Sichere die Ursache mit einem Test ab, der ohne die Korrektur fehlschlägt.
Was du danach in derselben Zeit erreichst
Der Unterschied zwischen einer halben Stunde und zwei Tagen liegt selten am Werkzeug, sondern an der Reihenfolge. Erst reproduzierbar machen, dann eingrenzen, dann den Zustand lesen, und erst zum Schluss ändern.
Den Fehler zuerst reproduzierbar machen
Solange du ihn nicht auslösen kannst, kannst du auch nicht prüfen, ob du ihn behoben hast. Feste Eingabedaten, ein bekannter Ausgangszustand und eine notierte Schrittfolge sind Voraussetzung, nicht Fleißarbeit.
Bedingte Haltepunkte statt Ausgaben
Ein Haltepunkt mit der Bedingung index === 417 hält genau im Fehlerfall an und ignoriert die 416 harmlosen Durchläufe davor. Der Code bleibt dabei unverändert, du musst also nichts wieder entfernen.
Den ersten eigenen Rahmen finden
Setz Framework- und Bibliotheksdateien auf die Ignore-Liste, dann zeigt der Aufrufstapel oben deine eigene Datei. Der Fehler entsteht fast nie in der Bibliothek, sondern in dem, was du ihr übergeben hast.
Rückwärts durch den Aufrufstapel gehen
Wenn ein Wert an der Fehlerstelle falsch ist, klick dich im Aufrufstapel nach oben und prüf ihn in jedem Rahmen. Die Ursache liegt an der Grenze, an der er das letzte Mal richtig war.
Source Maps auch für den Betrieb bereitstellen
Ohne Zuordnung ist ein Stack aus dem Betrieb wertlos. Mit versteckten Source Maps, die nur an die Fehlerüberwachung gehen, bekommst du lesbare Stacks, ohne den Quelltext auszuliefern.
Jede gefundene Ursache absichern
Ein Test, der ohne die Korrektur fehlschlägt, ist der einzige Beleg dafür, dass du die Ursache und nicht nur ein Symptom getroffen hast. Er verhindert außerdem, dass derselbe Fehler beim nächsten Umbau zurückkommt.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Was der Aufrufstapel wirklich sagt
Die Meldung selbst grenzt schon stark ein, wenn man sie genau liest. x is not defined ist ein ReferenceError: Der Name existiert in keinem erreichbaren Gültigkeitsbereich, also fehlt ein Import, ein Tippfehler steckt drin, oder die Datei wird im falschen Modulformat gelesen. Cannot read properties of undefined (reading 'name') heißt dagegen, dass das Objekt links vom Punkt undefined ist, nicht die Eigenschaft. Und x is not a function heißt, dass die Eigenschaft existiert, aber nichts Aufrufbares enthält, ein klassisches Zeichen für einen falschen Standardimport aus einem CommonJS-Paket.
Der oberste Rahmen im Stapel ist die Stelle, an der es geknallt hat, nicht unbedingt die Stelle mit dem Fehler. Interessant ist der Übergang: der letzte Rahmen, in dem der Wert noch richtig war. Deshalb ist die Ignore-Liste so nützlich: Sobald Bibliotheksrahmen ausgeblendet sind, beginnt der Stapel mit deinem Code, und beim schrittweisen Ausführen springt der Debugger nicht mehr in fremde Dateien.
Bei asynchronem Code zeigen die Entwicklungswerkzeuge zusätzlich die Rahmen vor dem await, gekennzeichnet als asynchroner Teil. Fehlt der Stapel ganz und in der Konsole steht nur Uncaught (in promise), gibt es kein await und kein catch an dieser Stelle. Beim Weiterreichen von Fehlern lohnt sich außerdem die Option cause: new Error('Preis nicht ladbar', { cause: fehler }) behält den ursprünglichen Fehler samt seinem Stapel.
Haltepunkte, die viele nie benutzt haben
Ein Rechtsklick auf die Zeilennummer bietet mehr als den einfachen Haltepunkt. Ein bedingter Haltepunkt hält nur an, wenn ein Ausdruck wahr ist, etwa bei einer bestimmten Kennung. Ein Logpoint gibt einen Ausdruck aus, ohne anzuhalten und ohne dass du eine Zeile im Code änderst, was die Frage nach vergessenen Ausgaben vollständig erledigt.
Für Fehler ohne bekannte Stelle gibt es Haltepunkte ohne Zeile. Anhalten bei Ausnahmen erwischt den Moment des Wurfs, und die Zusatzoption für abgefangene Ausnahmen findet die Fälle, die ein zu breites catch verschluckt. DOM-Haltepunkte halten an, wenn ein Element verändert oder entfernt wird, was die Frage beantwortet, welcher Code eine Klasse wieder wegnimmt. Ereignis-Haltepunkte greifen bei jedem Zuhörer eines Typs, und ein Haltepunkt auf eine Anfrage mit einem Teil der URL erwischt den Aufrufer eines fetch, ohne dass du ihn vorher kennst.
Zwei Kleinigkeiten sparen viel Zeit. In der Konsole hält debug(funktionsName) beim nächsten Aufruf dieser Funktion an, ohne dass du ihre Datei suchen musst. Und ein debugger-Ausdruck im Code ist für schwer erreichbare Stellen legitim, gehört aber nie in einen Zweig, der zusammengeführt wird. Die Regel no-debugger im Linter verhindert genau das.
Source Maps, und warum sie im Betrieb oft fehlen
Eine Source Map ordnet jede Position im minimierten Bündel einer Position im Quelltext zu. Der Browser findet sie über einen Kommentar am Dateiende, und ohne sie zeigt jeder Stack auf eine einzige lange Zeile. Im Entwicklungsserver ist das eingeschaltet, im Produktionsbau oft nicht, weil niemand den Quelltext ausliefern will.
Der Ausweg heißt versteckte Source Maps: Der Bau erzeugt die Dateien, lässt aber den Verweis am Ende des Bündels weg und lädt sie stattdessen zur Fehlerüberwachung hoch. Dort werden die eingehenden Stacks zugeordnet, während im Browser nichts Zusätzliches liegt. In den Entwicklungswerkzeugen lässt sich eine Map außerdem von Hand nachreichen, über Add source map im Kontextmenü der Datei.
Eine falsche Zuordnung ist schlimmer als gar keine. Wenn nach einem Umbau der Bau-Kette die Zeilen um ein paar Stellen verschoben sind, zeigt der Debugger auf eine Anweisung, die mit dem Fehler nichts zu tun hat, und du suchst an einer erfundenen Stelle. Ein kurzer Test gehört deshalb zur Bau-Kette: einen Fehler bewusst auslösen und prüfen, ob der Stapel auf die richtige Zeile zeigt.
Fehler, die nur bei anderen auftreten
Die häufigsten Unterschiede lassen sich der Reihe nach abarbeiten. Erweiterungen im Browser verändern Seiten, deshalb ist ein privates Fenster der schnellste Ausschlusstest. Ein alter Stand im Zwischenspeicher trifft auf eine neue Schnittstelle, was sich an unpassenden Feldern in den Antworten zeigt. Sprache und Zeitzone unterscheiden sich, und das trifft vor allem Datumsverarbeitung: Ein Datum ohne Zeitzone wird je nach Umgebung anders gelesen, und die Anzeige weicht um einen Tag ab.
Danach kommen die Unterschiede in den Daten. Sonderzeichen, sehr lange Werte, leere Listen und Datensätze aus der Zeit vor einer Umstellung erzeugen Zustände, die in Testdaten nicht vorkommen. Wenn du an den echten Datensatz herankommst, ist der Fehler meist in Minuten reproduziert, und die vorherige Suche im Code wird überflüssig.
Bleibt der Fehler unsichtbar, hilft nur Aufzeichnung aus dem Feld. Eine Fehlerüberwachung mit hochgeladenen Source Maps liefert lesbare Stacks samt Browser, Version und den letzten Schritten davor. Wichtig ist dabei, dass die Meldungen eine eigene Kennung je Vorfall bekommen, sonst lassen sich Bericht und Aufzeichnung später nicht zusammenbringen.
Wann Ausgaben trotzdem das bessere Mittel sind
Es gibt Fälle, in denen ein Haltepunkt die Sache verfälscht. Alles, was mit Zeit zu tun hat, verändert sich, sobald der Debugger anhält: Animationen, Zeitgeber, Rennen zwischen zwei Anfragen. Auch bei sehr häufigen Aufrufen ist Anhalten unbrauchbar, weil du hundertmal fortsetzen müsstest. Dann sind Ausgaben richtig, am besten als Logpoint statt als Codeänderung.
Für die Darstellung lohnt sich mehr als console.log. console.table zeigt eine Liste von Objekten als Tabelle mit Spalten, console.group fasst zusammengehörige Ausgaben zusammen, console.count zählt Durchläufe ohne eigene Variable, und console.trace gibt an Ort und Stelle den Aufrufstapel aus, was die Frage nach dem Aufrufer sofort beantwortet.
Was Ausgaben nicht ersetzen: den Blick auf den vollständigen Zustand. Eine Ausgabe zeigt genau die Werte, die du dir vorher ausgesucht hast, und Objekte werden in der Konsole erst beim Aufklappen ausgelesen, sodass du unter Umständen einen späteren Stand siehst als den zum Zeitpunkt der Ausgabe. Wer das weiß, gibt bei veränderlichen Objekten lieber eine Kopie aus.
Dazu passende Kurse
Mit einem festen Vorgehen kannst du einen Fehler systematisch einkreisen , statt dich von Symptom zu Symptom zu hangeln, und genau das üben die JavaScript-Kurse bei cmt an vorbereiteten Fällen.
Wenn du Fehler nicht nur finden, sondern ihr Wiederauftreten verhindern willst, führt der Weg über Kurse rund um systematisches Testen .
Wie sicher bist du beim Thema wirklich?
Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.
Wo du genau das übst
Ein sehr gutes, praxisorientiertes und nachhaltiges Seminar. So sollte es immer sein. Vielen Dank.
Die Schulung ist gerade für Einsteiger oder Entwickler*innen mit eingestaubtem Basiswissen sehr hilfreich.
Ich bin sehr glücklich Franz als Trainer gehabt zu haben. Er hat offensichtlich unglaublich viel Wissen zu den Themen, und schafft es, dieses auch interaktiv und verständlich weiterzugeben.
Häufige Fragen
Wie finde ich den Auslöser, wenn der Stapel nur Framework-Dateien zeigt?
Der Fehler tritt nur im Produktionsbau auf, nicht im Entwicklungsserver. Woran liegt das?
Wie kann ich im Betrieb prüfen, ob eine Änderung hilft, ohne neu auszuliefern?
Lohnt sich der debugger-Ausdruck im Code?
Deine Ansprechpartner
Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.
Yves Hoppe
Weiterbildung & Beratung
Hilft dir, aus dem Webentwicklung-Programm den passenden Kurs für deinen Stand zu finden.
Norbert Jansen
Beratung & Inhouse
Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.
Fehler, die du nicht mehr durch Ausprobieren findest
In den JavaScript-Kursen bei cmt arbeitest du mit vorbereiteten Fehlern und gehst dabei vom Symptom über den Haltepunkt bis zur Ursache, die du belegen kannst.