mitmario.dev

Debuggen

Node.js Sandbox 5 Min Lesezeit 3 BeispieleLektion 3 von 7

Es gibt zwei Arten, einen Fehler zu suchen. Die eine ist, zwanzig console.log zu verteilen, bis eine Zeile etwas Unerwartetes zeigt. Die andere ist, das Programm anzuhalten und nachzusehen. Beide sind richtig, und der Unterschied ist nicht Geschmack, sondern die Frage, ob du weißt, wonach du suchst.

Warum Fehler in Node so still sind

Stille Umwandlungen
// Sechs Werte, drei Umwandlungen. Keine davon wirft, keine warnt.
const proben = ["24,90", "", "  ", "08", "12.5kg", undefined];

for (const roh of proben) {
  console.log(
    `${JSON.stringify(roh)}`.padEnd(10),
    "Number:", String(Number(roh)).padEnd(6),
    "parseFloat:", String(Number.parseFloat(roh)).padEnd(6),
    "parseInt:", String(Number.parseInt(roh, 10))
  );
}

const preis = Number.parseFloat("24,90");

console.log("");
console.log("Zwei Stueck zu 24,90 kosten laut Programm:", preis * 2);

JavaScript wirft bei diesen Umwandlungen nicht. Es liefert NaN, 0 oder eine halb gelesene Zahl, und das Programm rechnet damit weiter. Der erste Fall ist der, der in der Praxis am häufigsten Geld kostet: parseFloat liest bis zum Komma und hört dann auf, aus 24,90 werden also 24.

Eine Ausgabe findet so etwas nur, wenn du sie an genau die Stelle setzt, die du verdächtigst. Ein Haltepunkt findet es, ohne dass du vorher eine Vermutung hast, denn er zeigt dir alle Variablen.

node —inspect und was dabei passiert

Startest du dein Programm mit node --inspect index.js, öffnet der Prozess zusätzlich einen Zugang und schreibt eine Adresse auf den Fehlerkanal. Daran hängt sich dann entweder dein Editor oder die Entwicklerwerkzeuge deines Browsers, und du bekommst genau das, was du aus dem Browser kennst: Haltepunkte, Variablenansicht, schrittweises Weitergehen.

Zwei Dinge sind dabei wichtig:

  • --inspect-brk hält schon vor der ersten Zeile an. Das brauchst du immer dann, wenn der Fehler beim Start passiert, denn mit --inspect allein ist das Programm längst durch, bevor du dich verbunden hast.
  • Der Zugang ist ein offener Zugang. Auf dem eigenen Rechner ist das egal, auf einem Server nicht: Wer sich verbindet, darf beliebigen Code in deinem Prozess ausführen. --inspect gehört deshalb in die Entwicklung und nicht in den Betrieb.

Und ja, console.log bleibt trotzdem oft die bessere Wahl. Wenn etwas hundertmal passiert und du wissen willst, wie sich ein Wert über die Durchläufe entwickelt, ist ein Haltepunkt hundertmal lästig und eine Ausgabe einmal geschrieben.

Die erste Hälfte kannst du hier sehen, die zweite nicht

Hol die Aufgabe dieser Lektion nach vorn und tipp im Terminal:

node --inspect rechnung.js

Auf dem Fehlerkanal steht dann eine Zeile wie Debugger listening on ws://127.0.0.1:9229/9e773c24-5a1e-40ae-891a-ea2f8aff4e8b, darunter läuft dein Programm ganz normal weiter und gibt seine Summe aus. Genau das passiert auch auf deinem Rechner.

Verbinden kann sich hier allerdings niemand. Die Adresse zeigt auf 127.0.0.1 in der Sandbox, und aus dieser Maschine führt nur ein einziger Port nach draußen, nämlich der, auf dem im Reiter „Browser” deine Seite steht. Dein Editor und dein Browser sitzen woanders. Probier ruhig node --inspect-brk rechnung.js: Der Prozess wartet dann auf einen Debugger, der nie kommt, gibt nichts aus und wird nach dreißig Sekunden vom Zeitlimit des Terminals beendet. Nachsehen, ob noch etwas läuft, geht auch nicht, ps gibt es in diesem Abbild nicht.

Das ist keine Lücke des Kurses, sondern die Rechnung dahinter: Ein offener Debugger-Zugang ist ein offener Zugang, und die Sandbox hat aus gutem Grund keinen.

Der Reiter Debug

Anhalten geht also nicht. Die Frage, für die man anhält, beantwortet trotzdem etwas, nämlich der Reiter Debug, den du aus dem JavaScript-Kurs kennst. Ein Druck auf Aufzeichnen, und dein Programm läuft noch einmal, diesmal mit einer Meldung vor jeder Zeile: was stand in diesem Moment in den Variablen. Danach blätterst du durch die Liste, und der Editor springt mit.

Probier das an der Aufgabe dieser Lektion, und zwar bevor du etwas änderst. Im Startzustand sind es neunzehn Schritte. Im vierten steht text: "24,90", das ist der Rohwert aus dem Formular, und im fünften daneben zeile: 4800. Zwei Tastaturen zu 24,90 sind 49,80 Euro, hier werden 48 daraus. Ab da kannst du beim Weiterblättern zusehen, wie sich der Fehler aufsummiert: summe steht auf 4800, dann 8400, dann 10000, dann 10800, und am Ende auf den 14000, die als 140,00 EUR ausgegeben werden.

Das Entscheidende daran ist nicht, dass die Zahl falsch ist. Das sagt dir die Prüfliste auch. Das Entscheidende ist, dass du sie siehst, ohne vorher gewusst zu haben, wonach du suchen musst. Genau das ist der Unterschied aus dem ersten Absatz dieser Lektion.

Zwei Sachen dazu, damit du nicht daneben greifst. Aufgezeichnet wird die Datei, die im Editor gerade vorn liegt, hier also rechnung.js. Und eine Aufzeichnung ist ein eigener Lauf: Sie kostet wie jeder andere, und die Prüfliste füllt sie nicht.

Halbieren, prüfen, halbieren

Halbieren statt raten
// Fuenf Schritte, ein falsches Ergebnis am Ende.
const schritte = [
  (wert) => wert.trim(),
  (wert) => wert.toLowerCase(),
  (wert) => wert.replaceAll(" ", "-"),
  (wert) => wert.replaceAll(/[^a-z0-9-]/g, ""),
  (wert) => wert.replaceAll(/-+/g, "-"),
];

function laufe(eingabe, bis) {
  let wert = eingabe;
  for (let i = 0; i < bis; i++) wert = schritte[i](wert);

  return wert;
}

const eingabe = "  Grüße aus Köln  ";

// Nicht vorn anfangen, sondern in der Mitte: Ist das Ergebnis nach
// drei Schritten noch gut, liegt der Fehler hinten.
console.log("nach 3 von 5:", JSON.stringify(laufe(eingabe, 3)));
console.log("nach 4 von 5:", JSON.stringify(laufe(eingabe, 4)));
console.log("nach 5 von 5:", JSON.stringify(laufe(eingabe, 5)));

Die Methode, die am zuverlässigsten funktioniert und sich am langsamsten anfühlt: Nimm die Strecke zwischen „hier ist es noch richtig” und „hier ist es falsch” und schau in die Mitte. Danach ist die Strecke halb so lang. Bei fünf Schritten brauchst du so zwei Blicke statt fünf, bei zwanzig fünf statt zwanzig.

Das gilt nicht nur für Zeilen. Es gilt genauso für Commits (git bisect macht genau das), für Eingabedaten (die Hälfte der Zeilen weglassen) und für Konfiguration (die Hälfte der Optionen ausschalten).

Ausgaben, die etwas sagen

Ausgaben, die etwas taugen
const position = { artikel: "Kabel", preis: "4,95", menge: 4 };
const zeile = 1980;
const summe = 14850;

// Drei Ausgaben derselben Zahlen. Die erste sagt nicht, was sie sind.
console.log(zeile);
console.log("zeile", zeile, "summe", summe);
console.log({ zeile, summe });

// Bei mehreren gleich gebauten Objekten spart console.table die Arbeit.
console.table([position, { artikel: "Maus", preis: "12,50", menge: 3 }]);

// Und der Kanal zaehlt: Wer beim Suchen console.error nimmt, findet
// seine Zeilen auch dann wieder, wenn stdout weitergeleitet wird (4.3).
console.error("suche: hier bin ich");

Wenn es doch eine Ausgabe wird, dann eine, die man später noch lesen kann. console.log({ zeile, summe }) schreibt die Namen gleich mit, und das ist ein Zeichen Aufwand gegenüber console.log(zeile, summe). Bei mehreren gleich gebauten Objekten zeigt console.table sie als Tabelle mit einer Spalte je Feld.

Und ein Kniff, der Zeit spart: Suchausgaben auf console.error schreiben. Sie gehen dann auf den Fehlerkanal, stehen also getrennt von allem, was dein Programm normal ausgibt, und du kannst sie mit node index.js > /dev/null alleine sehen. Das ist Lektion 4.3, angewendet auf die eigene Arbeit.

node —watch

Zum Schluss die kleine Bequemlichkeit, die viele noch mit einem Paket lösen: node --watch index.js startet dein Programm neu, sobald sich eine Datei ändert, die es geladen hat. Dafür gab es jahrelang nodemon, und für die meisten Projekte braucht es das nicht mehr.

Zusammen mit --inspect lässt sich beides kombinieren, und dann hast du eine Arbeitsumgebung, die ohne eine einzige Abhängigkeit auskommt.

Auch davon siehst du hier nur die eine Hälfte. Tipp node --watch rechnung.js, und im Terminal steht nach dem Durchlauf Completed running 'rechnung.js'. Waiting for file changes before restarting.... Weiter kommt es nicht: Ändern müsstest du die Datei im Editor, und solange ein getippter Befehl läuft, hält die Sitzung still und spielt nichts ein. Es fehlt schlicht der zweite Schreiber. Auf deinem Rechner ist der zweite Schreiber dein Editor, und dann steht dort Change detected und Restarting.

Zum Mitnehmen

Ein Haltepunkt zeigt dir alle Variablen. Eine Ausgabe zeigt dir die, an die du vorher gedacht hast. Der Unterschied ist genau dann groß, wenn du nicht weißt, wonach du suchst.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.