Ijs2019medienpartner.jpg

SELFHTML ist in diesem Jahr Medienpartner der IJC.

Für die Konferenz vom 21. – 25. Oktober 2019 in München verlosen wir ein Freiticket.

Weitere Informationen finden sich im SELFHTML-Forum.

JavaScript/Tutorials/Einstieg/Debuggen

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Informationen zu diesem Text

Lesedauer
45min
Schwierigkeitsgrad
mittel
Vorausgesetztes Wissen
Grundkenntnisse in JavaScript

Programmieren beinhaltet immer auch die Suche nach Fehlern. Neben Syntaxfehlern existieren auch logische Fehler. In JavaScript erzeugen Fehler oft keinen Abbruch oder eine Fehlermeldung und sorgen erst viel später für Probleme, deren Ursache dann durch Debuggen herausgefunden werden muss.

In diesem Tutorial lernen Sie, wie Sie Scripte auf Programmierfehler überprüfen und debuggen können. Als wichtiges Entwicklungswerkzeug, das die JavaScript-Programmierung entscheidend erleichtert, verwenden wir die eingebaute Konsole.

Damit können Sie ...

und

bisherige Vorgehensweise[Bearbeiten]

In einem ersten Schritt können Sie zum Testen einfach Blöcke auskommentieren und so Schritt für Schritt überprüfen, welche Zeilen/ Blöcke im Code nicht funktionieren.

Mit Hilfe von window.alert können Sie eine Kontrollausgabe von Variablen und den ihnen zugewiesenen Werten vornehmen. Wurde ein alert ausgeführt, wusste man, dass der Code bis dahin funktionierte. Nachteile dieser Methode waren, dass ein alert das Programm anhielt und erst nach einer Bestätigung des Nutzers weiterlaufen ließ. Mehrere alerts überlasteten schnell den Speicher.

Eventuell ist der Fehler nicht in dieser Zeile, sondern schon in einer der vorhergehenden zu finden. Versuchen Sie nachzuvollziehen, warum es in dieser Zeile zu dem Fehler kommt.

Weiterhin können Sie während des Entwickelns mit try ... catch Fehler ermitteln und als Fehlermeldung ausgeben.

Heutzutage können diese Fehlersuchen bequem durch die browsereigene Konsole durchgeführt werden.

Kontrollausgabe mit der Konsole[Bearbeiten]

Die Konsole öffnen[Bearbeiten]

Die Konsole öffnet sich unterschiedlich, je nach Browser. Das kann zum einen über die jeweiligen Menüs, über die Funktionstaste F12, oder aber durch eine der folgenden Tastenkombinationen geöffnet werden:

  • Mozilla Firefox: unter Windows und Linux mit Strg + Umschalt + I, unter Mac mit Cmd + Opt + I
  • Google Chrome: unter Windows und Linux mit Strg + Umschalt + J, unter Mac mit Cmd + Opt + J
  • Microsoft Edge und Internet Explorer: F12 anschließend Strg + 2

Screenshot

(Ansicht der Firebug-Konsole im Firefox Version 40)


Debuggen mit Haltepunkten[Bearbeiten]

Durch die oben aufgeführten Kontrollausgaben können Sie den Ablauf eines Scriptes nachvollziehen und Variablen ausgeben, um deren Werte zu prüfen. Selbst minifizierter Code kann wieder lesbar formatiert werden.

Ist Ihnen rechts neben der Konsole der Debugger (Überwachen) aufgefallen? Er ist ein vielseitiges und mächtiges Werkzeug, mit dem Sie die Funktionsweise eines Scriptes komfortabler untersuchen können, denn Sie müssen nicht nach jeder Anweisung eine Kontrollausgabe einfügen. Ein JavaScript-Debugger bietet im Allgemeinen folgende Möglichkeiten:

Setzen von Haltepunkten[Bearbeiten]

Sie können Haltepunkte (englisch Breakpoints) setzen, indem sie in der Konsole im Quelltext erst oben links auf das Symbol klicken und dann die einzelnen Zeilennummern markieren. Die Ausführung des JavaScripts wird an dieser Stelle unterbrochen und Sie können alle Variablen und Objekte rechts im Debugger sehen und überprüfen.

Screenshot

Mit einem Klick auf den aktuellen Haltepunkt, der nun ein Play-Symbol beinhaltet, können Sie das Script von dieser Code-Zeile aus wieder zum Laufen bringen und die folgenden Anweisungen nun Schritt für Schritt ausführen, Anweisungen überspringen und aus aufgerufenen Funktionen herausspringen. Bei dieser schrittweisen Ausführung können Sie überprüfen, welche Werte bestimmte Objekte und Variablen an dieser Stelle im Script haben.

Call Stack[Bearbeiten]

Bei komplerexen Programmen mit mehreren Funktionen, die sich gegenseitig aufrufen, hilft Ihnen ein Debugger, die Übersicht über das Aufrufen und Abarbeiten von Funktionen zu behalten. In der Einzelschritt-Ausführung haben Sie den sogenannten Call Stack (englisch für Aufruf-Stapel) im Blick. Das ist die Verschachtelung der Funktionen, die gerade abgearbeitet werden. Wenn beispeilsweise die Funktion a die Funktion b aufruft und diese wiederum die Funktion c, so ist der Stapel a > b > c. Die Funktion c, die gerade ausgeführt wird, liegt sozusagen oben auf dem Stapel. Nach dessen Ausführung wird c vom Stapel genommen und in die Funktion b zurückgekehrt - und so weiter.

Fallen und Fehlerquellen[Bearbeiten]

runde oder geschweifte Klammern[Bearbeiten]

Alle Klammern die geöffnet werden, müssen auch wieder geschlossen werden!
Ist eine Klammer zuviel im Script-Code?

Hochkommata um Strings[Bearbeiten]

Fehlt ein schließendes Hochkomma oder wird ein doppeltes Hochkomma durch ein einfaches Hochkomma geschlossen?

Bindestrich im Variablennamen?[Bearbeiten]

Wenn Sie eine Variable mit einem Bindestrich schreiben, wird aus ihrem Namen eine Zuweisung, da JavaScript den Bindestrich als Rechenoperator ansieht und versucht die beiden Teile der Variablen zu substrahieren.

Groß- und Kleinschreibung[Bearbeiten]

Javascript ist case-sensitive, dass heißt, das nameVorname und namevorname unterschiedliche Variablen sind. Sie geben in der Konsole im strengen Modus eine Fehlermeldung.

Empfehlung: Verwenden Sie das so genannte CamelCase, in dem jeweils der erste Buchstabe der einzelnen Bestandteile des Namens groß geschrieben wird.
Beispiel:
DiesIstEineVariable.

Schreibfehler in einer der DOM-Methoden? Vor allem Methoden wie getElementById werden oft falsch geschrieben. Hier hilft ein Web-Editor mit autocomplete, der Methoden und mehrmals verwendete Variablen automatisch vorschlägt.

oktale Syntax[Bearbeiten]

Besonders im Bereich der Datumsberechnung werden häufig Zahlen mit führender Null benutzt. Einfache Aufgaben wie alert(08 * 60) liefern dann scheinbar falsche Ergebnisse oder gar Fehlermeldungen.

Des Rätsels Lösung dafür ist, dass JavaScript Zahlen mit führender 0 als Oktalzahlen interpretiert, nicht als Dezimalzahlen. Die Notation von Oktalzahlen mit einer führenden Null ist eine in vielen Programmiersprachen verbreitete Konvention, die ursprünglich von C kommt.

Mit einer Anweisung wie parseInt("08", 10) können Sie das Problem vermeiden und erzwingen, dass der Wert dezimal interpretiert wird.

Empfehlung: Verwenden Sie den strict mode, um die Interpretation als Oktalzahlen auszuschließen.

Siehe auch[Bearbeiten]


Quellen[Bearbeiten]


ToDo (weitere ToDos)

Ist Ihnen aufgefallen, dass alle Beispiel in diesem Artikel funktionieren?

Besser wäre es, die Methoden der Console API in einen eigenen Artikel auszulagern und stattdessen fehlerhafte Skripte vorzustellen, die der Leser selbst debuggen muss.


>> Der Programmierer meinte wenn man statt eines . ein , verwendet dann führt das zu einem Javascript Fehler.

Nein, nicht unbedingt. Es führt aber zu einem völlig anderem Ergebnis:

1,414 + 1,732 ergibt: 732. Was passiert, ist:

   Werte den Ausdruck vor dem ersten , aus: 1.
   Werte den nächsten Ausdruck aus: 414 + 1 ergibt 415.
   Werte den nächsten Ausdruck aus: 732.
   Der zurückgegebene Wert ist der letzte, also 732.

Hingegen: 1.414 + 1.732 ergibt 3.146.

Beispiel
var a = 23;
var b = 42;
if (a = b) {
  alert('23 ist 42, q.e.d.');
}

Oft sind es die kleinen Fehler, die man übersieht, auch wenn man sich den Code dreimal anschaut. Zumindest könnte man mit einem console.log(a) vor der if-Abfrage bemerken, dass der Inhalt von a noch stimmt, innerhalb dann aber den Inhalt von b angenommen hat, was nicht beabsichtigt war. Und so muss der Fehler zwischen den beiden console.log() sitzen.

Beispiel
var a = 23;
var b = 42;
console.log(a, b);
if (a = b) {
  console.log(a, b);
  alert('23 ist 42, q.e.d.');
}

Self-Forum: Wiki: Tutorial Grundlagen von Strings und Arrays vom 20.04.2017

--Matthias Scharwies (Diskussion) 06:28, 21. Apr. 2017 (CEST)

Fehlervermeidung[Bearbeiten]

ToDo (weitere ToDos)

Ein guter Fehler

> > > var durchlauf = 1;

> > Oh Mann, und ich hab schon geglaubt, dass der Grundaufbau falsch war. Wenn ich genau drüber nachdenk', ist dieser Fehler aber schlimmer. :-(

> Ach Quark! – Das war ein wirklich guter Fehler! :-)

Gut, weil leicht zu finden und billig zu beseitigen. Schusseligkeiten leistet sich jeder, ausnahmslos. Deshalb sollte man bei sowas nicht zu hart mit sich ins Gericht gehen. Strukturelle Fehler sind da viel schlimmer, weil man sich erstens eingestehen muss, dass man das Ganze nicht richtig durchdacht hat, und weil natürlich zweitens die Korrektur mit bedeutend mehr Aufwand verbunden ist.

Du hattest ja selbst schon festgestellt, dass zahlSumme[durchlauf] der Übeltäter sein musste, der am Ende undefined zurückgegeben hat. Die Überprüfung der Indexvariable wäre der nächste logische Schritt zur Fehlerermittlung gewesen.

Wobei du anstatt direkt im Code nachzusehen, freilich auch einfach eine Testausgabe der beteiligten Werte in der Konsole hättest machen können, also des Arrays und der Indexvariable. Dann hättest du festgestellt, dass die erste ausgegebene Summe der zweite Wert aus dem Array ist, und du hättest gesehen, dass die Indexvariable als erstes den Wert 1 hat, und dich dann daran erinnert, dass Arrays in ECMAScript wie in den allermeisten anderen Programmiersprachen einen 0-Index haben, und damit wäre das Problem gelöst gewesen.

Ein echter Klassiker! ;-)

Ich entwickle seit etwa einem Jahr eine Grafik-Engine basierend auf ECMAScript und WebGL, ohne Zuhilfenahme externer Bibliotheken. Eine echt unglaublich komplexe wie komplizierte Aufgabe, die mich hinsichtlich meiner Art zu Programmieren wirklich Mores gelehrt hat! :-D

Tatsächlich gestaltet sich das „Programmieren“ bei mir in der Regel so, dass ich ersteinmal für viele Stunden wie Rodin’s Denker dasitze, über die Anforderungen und Lösungswege nachdenke und dann mit Stift und Papier die vorläufigen Ergebnisse meiner Überlegungen in Flussdiagrammen aufzeichne.

Wenn ich irgendwann das Gefühl habe, die entsprechenden Pläne würden einigermaßen Sinn ergeben, dann öffne ich die Datei in der ich meinen Code dokumentiere, erstelle ein neues Kapitel und beginne mit einer Kurzen inhaltlichen Zusammenfassung der entsprechenden Programmroutine.

Dann definiere ich die Eingaben und Ausgaben, sprich, welche Parameter von welchem Typ nimmt die Funktion entgegen, und welche Variablen von welchem Typ sollen am Ende ausgegeben werden.

Wenn Eingabe und Ausgabe klar definiert sind, schreibe ich dann in natürlicher Sprache einen detaillierten Algorithmus auf, der keinerlei Rücksicht auf Performanz nimmt oder auch nur in irgendeiner Form die tatsächliche spätere Implementierung antizipieren würde, sondern der einzig und allein dazu dient, den Programmablauf widerspruchsfrei zu beschreiben. Das heißt, undefinierte Programmzustände müssen soweit wie es möglich ist ausgeschlossen sein.

Wenn der Algorithmus dann fertiggestellt ist und ich keine logischen Fehler erkennen kann, dann füge ich meistens noch Kommentare hinzu, die einerseits dazu dienen, bestimmte Punkte noch ausführlicher zu beschreiben, andererseits aber auch den Zweck haben, die dahinterstehende Motivation zu begründen, damit man beim späteren Durchlesen den Faden leichter wieder aufnehmen kann.

Erst dann fange ich mit der tatsächlichen Implementierung des Algorithmus an! :-)

Dabei ist das Ziel dann zunächst einmal, die Routine unter Anwendung von best practices so zu schreiben, dass sie das in dem Algorithmus beschriebene Verhalten exakt widergibt, was natürlich auch entsprechende Tests beinhaltet. Ist dieses Ziel dann erreicht, wird gegebenenfalls schließlich noch auf Performanz getestet, verglichen, und das Ganze dann eventuell neu aufgesetzt und erneut getestet.

Je nach Komplexität des Algorithmus’ fällt diese Phase mal kürzer und mal länger aus. Allerdings tendiere ich dazu, meine Funktionen klar abgegrenzte Aufgaben ausführen zu lassen und monolithische Konstrukte zu vermeiden, sodass die Implementierung üblicherweise relativ zügig abgeschlossen werden kann und sie schließlich nur einen kleinen Teil der Zeit in Anspruch nimmt…

Du wirst jetzt wahrscheinlich denken, dieser Aufwand wäre vollkommen verrückt und unangemessen, aber glaube mir, insbesondere wenn man an einem so komplexen System arbeitet, ist Fehlervermeidung weit weniger teuer als Fehlerbeseitigung! :-D

Viele Grüße,

Orlok

klare Programmstruktur und Kommentare[Bearbeiten]

Im Übrigen macht eine klare Programmstruktur die Fehlersuche deutlich einfacher. Das heißt, es gilt monolithische Konstruktionen zu vermeiden und statt dessen die verschiedenen Aufgaben auf relativ kurze und gut überschaubare Funktionen zu verteilen, wobei dann auch nicht mit Kommentaren gespart werden sollte.

Rumprobieren[Bearbeiten]

Rumprobieren ist in der Regel allerdings nicht die effizienteste Methode um Fehlerursachen zu identifizieren. Das heißt, wann immer irgendwas nicht funktioniert, sollte zunächst ein Blick auf die Konsole deines Browsers geworfen werden:

Wenn dein Programm mit einem Fehler abgebrochen hat, findest du dort entsprechende Hinweise, also um welche Art von Fehler es sich handelt, wie zum Beispiel …

  • ein Syntax Error, etwa wenn das Schließen einer Klammer vergessen wurde, oder
  • ein Reference Error, wenn du an einer Stelle versuchst über einen Bezeichner eine Variable zu referenzieren, die in dem jeweiligen Kontext (noch) nicht existiert (also wie mit event in deinem Frageposting), oder
  • ein Type Error, üblicherweise dadurch verursacht, dass an einer Stelle ein Objekt erwartet wird, aber statt dessen der primitive Wert undefined vorliegt, da die Übergabe vergessen wurde (siehe newElement in deinem Frageposting)

… sowie darüber hinaus auch immer eine Angabe dazu gegeben wird, an welcher Stelle, also in welcher Datei und in welcher Zeile der Fehler aufgetreten ist. Meistens genügt das schon, um den Fehler zu finden.

Manchmal funktioniert etwas aber auch nicht, ohne dass das Programm durch eine Ausnahme beendet wurde. In diesem Fall hilft die Eingrenzungstaktik, wobei man step by step diejenigen Teile des Programms ausschließt, die funktionieren wie gewünscht, bis dann schließlich der Teil gefunden ist, der nicht funktioniert. Dabei kann dann das debugger Statement hilfreich sein:

(function test (value) {
  'use strict';
  debugger;
  value = 'bar';
  debugger;
  value = 'baz';
  debugger;
}('foo'));

Wenn du deine Seite dann im Browser ausführst und bei den Entwicklertools den Debugger aufrufst, dann wird das Programm bei jedem Auftreten von debugger angehalten und es werden dir unter anderem die Werte der Variablen zu diesem Zeitpunkt angezeigt.

Hier im Beispiel würde das Programm also gleich nach Eintritt in die Funktion angehalten und du könntest sehen, dass die lokale Variable (Parameter) value den Wert foo hat. Wenn du nun im Debugger auf fortsetzen (oder so ähnlich) klickst, dann läuft das Programm bis zum nächsten debugger und hält dann wieder an, sodass du wieder die aktuellen Werte überprüfen kannst.

Das heißt, wenn das Script des Beispiels hier fortgesetzt wird, dann ist der nächste Stop nach der ersten Wertzuweisung und du könntest sehen, dass die Variable nunmehr den Wert bar hat, usw.

Davon abgesehen besteht natürlich auch immer die Möglichkeit, sich mittels der verschiedenen Methoden der console-Schnittstelle bestimmte Werte explizit anzeigen zu lassen. Dann ist entsprechend immer zu prüfen, ob die tatsächlichen Werte an einer Stelle mit den erwarteten Werten übereinstimmen. So kommt man der Fehlerursache Schritt für Schritt näher. ;-)

Weblinks[Bearbeiten]