2.5 Fehler finden, Fehler vermeiden
Während der Entwicklung eines Programms werden Sie den einen oder anderen Fehler machen. Das ist normal und gehört zum Alltag eines Entwicklers. Wichtig ist, dass Sie wissen, wie Sie Fehler schnell finden und beseitigen. Noch besser ist es, wenn Sie wissen, wie Sie Fehler möglichst vermeiden.
Es kann sich um syntaktische Fehler handeln, also das Notieren einer falschen oder unvollständigen JavaScript-Anweisung. Es können aber auch logische Fehler sein. D. h., Ihr Programm läuft einwandfrei, produziert aber nicht die von Ihnen erwarteten Ergebnisse.
2.5.1 Entwicklung eines Programms
Bei der Entwicklung Ihrer eigenen Programme sollten Sie Schritt für Schritt vorgehen. Stellen Sie zuerst einige Überlegungen an, wie das gesamte Programm aufgebaut sein sollte, und zwar auf Papier. Aus welchen Teilen sollte es nacheinander bestehen? Versuchen Sie dann nicht, das gesamte Programm mit all seinen komplexen Bestandteilen auf einmal zu schreiben! Dies ist der größte Fehler, den Einsteiger (und manchmal auch Fortgeschrittene) machen können.
Schreiben Sie zunächst eine einfache Version des ersten Programmteils. Anschließend testen Sie sie. Erst nach einem erfolgreichen Test fügen Sie den folgenden Programmteil hinzu. Nach jeder Änderung testen Sie wiederum. Sollte sich ein Fehler zeigen, so wissen Sie, dass er aufgrund der letzten Änderung aufgetreten ist. Nach dem letzten Hinzufügen haben Sie eine einfache Version Ihres gesamten Programms.
Nun ändern Sie einen Teil Ihres Programms in eine komplexere Version ab. Auf diese Weise machen Sie Ihr Programm Schritt für Schritt komplexer, bis Sie schließlich das gesamte Programm so erstellt haben, wie es Ihren anfänglichen Überlegungen auf Papier entspricht.
Manchmal ergibt sich während der praktischen Programmierung noch die eine oder andere Änderung gegenüber Ihrem Entwurf. Das ist kein Problem, solange sich nicht der gesamte Aufbau ändert. Sollte dies allerdings der Fall sein, so kehren Sie noch einmal kurz zum Papier zurück und überdenken den Aufbau. Das bedeutet nicht, dass Sie die bisherigen Programmzeilen löschen müssen, sondern möglicherweise nur ein wenig ändern und anders anordnen.
Schreiben Sie Ihre Programme übersichtlich. Falls Sie gerade überlegen, wie Sie drei, vier bestimmte Schritte Ihres Programms auf einmal machen können: Machen Sie daraus einfach einzelne Anweisungen, die der Reihe nach ausgeführt werden. Dies vereinfacht eine eventuelle Fehlersuche. Falls Sie (oder eine andere Person) Ihr Programm später einmal ändern oder erweitern möchten, gelingt der Einstieg in den Aufbau des Programms wesentlich schneller.
Einige typische Fehler im Zusammenhang mit Verzweigungen und Schleifen habe ich bereits erwähnt. Haben Sie alle Klammern, die Sie geöffnet haben, auch wieder geschlossen? Ein klassischer Programmierfehler, der speziell bei JavaScript häufig dazu führt, dass überhaupt nichts angezeigt wird.
Sie haben auch bereits gesehen, wie Sie die Methode alert() zur Kontrolle von Werten und zur Suche von logischen Fehlern einsetzen. Sie können zusätzlich einzelne Teile Ihres Programms in Kommentarklammern setzen, um festzustellen, welcher Teil des Programms fehlerfrei läuft und welcher Teil demnach fehlerbehaftet ist.
2.5.2 Fehler finden mit »onerror«
Eine Möglichkeit, Fehler zu finden, bietet Ihnen das Ereignis onerror. Sobald bestimmte Fehler auftreten, wird das Ereignis ausgelöst und eine Funktion aufgerufen, die Ihnen Informationen über den Fehler liefert. Betrachten wir das folgende Programm:
...
<body>
<p><script>
var y = 42;
document.write(x + "<br>");
document.write(y + "<br>");
</script></p>
</body></html>
Listing 2.20 Datei »onerror.htm«, erste Version
Der Wert der beiden Variablen soll auf dem Bildschirm ausgegeben werden. Die Variable x wird aber nie deklariert. Der Versuch, x auszugeben, führt zu einem Abbruch des Programms. Auch der Wert der korrekt deklarierten Variablen y wird nicht mehr ausgegeben. Der Bildschirm bleibt leer.
Zur Fehlersuche wird das Programm erweitert:
<!DOCTYPE html><html><head><meta charset="utf-8">
<title>Fehler finden</title>
<script>
function fehlerbehandlung(fehler, datei, zeile)
{
alert("Fehler: " + fehler + "\nDatei: " + datei
+ "\nZeile: " + zeile);
}
</script>
</head>
<body>
<p><script>
window.onerror = fehlerbehandlung;
var y = 42;
document.write(x + "<br>");
document.write(y + "<br>");
</script></p>
</body></html>
Listing 2.21 Datei »onerror.htm«, zweite Version
Das Thema Ereignisse behandele ich ausführlich in Kapitel 4. In Abschnitt 2.6 sehen Sie, wie Sie Programme mithilfe von Funktionen modularisieren. An dieser Stelle nur eine kurze Erklärung:
Das Ereignis onerror tritt bei einem Fehler auf. Als Folge wird die Funktion aufgerufen, die dem Ereignis zugewiesen wurde. Hier ist dies die Funktion fehlerbehandlung, die im head des Dokuments definiert wird. Dieser Funktion werden Informationen zu dem aufgetretenen Fehler übermittelt, die Sie mithilfe von alert() ausgeben können.
Die Meldung für den vorliegenden Fall sehen Sie in Abbildung 2.24. Es wird ein Uncaught ReferenceError genannt. Der Bezeichner x stellt also keine Referenz zu einer Variablen dar. Dieser Fehler wird nicht gefangen (engl.: caught). Er tritt in der Datei onerror.htm in Zeile 18 auf.
Abbildung 2.24 Ausgabe bei einem Fehler
2.5.3 Ausnahmebehandlung mit »try … catch«
Die Ausnahmebehandlung (englisch: exception handling) mit try … catch ermöglicht es Ihnen, Ihr Programm vor einem Abbruch zu bewahren. Falls Sie wissen, dass in bestimmten Programmteilen aufgrund von Benutzereingaben oder aufgrund anderer Ursachen Fehler auftreten können, setzen Sie die betreffenden Programmteile in einen try-Block.
Sollte tatsächlich ein Fehler auftreten, verzweigt das Programm in einen catch-Block, in dem der Fehler gefangen wird. Dort kann eine Fehlermeldung ausgegeben werden, oder es können alternative Anweisungen durchlaufen werden, die die Folgen des Fehlers umgehen.
Falls kein Fehler auftritt, so werden die Anweisungen im try-Block normal abgearbeitet. Der catch-Block wird in diesem Fall übergangen. Es kann außerdem einen finally-Block geben, der in jedem Fall bearbeitet wird. Dies gilt unabhängig davon, ob ein Fehler aufgetreten ist oder nicht. Alle Blöcke müssen in Blockklammern notiert werden, auch wenn sie nur eine einzelne Anweisung umfassen. Ein Beispiel:
...
<body>
<p><script>
var y = 42;
try
{
document.write(x + "<br>");
document.write(y + "<br>");
}
catch(e)
{
alert(e);
}
finally
{
document.write("Das wird auf jeden Fall gemacht<br>");
}
</script></p>
</body></html>
Listing 2.22 Datei »try_catch.htm«
Im try-Block sollen die Werte der beiden Variablen ausgegeben werden. Die Variable x wird aber nie deklariert. Der Fehler wird bemerkt, und das Programm verzweigt in den catch-Block. In diesem Falle werden Informationen zu dem aufgetretenen Fehler in einem Fehlerobjekt übermittelt. Es hat sich eingebürgert, ein solches Fehlerobjekt mit e (für englisch: error) zu bezeichnen. Sie können es mit alert() ausgeben.
In der Ausgabe in Abbildung 2.25 wird ein ReferenceError genannt. Im Unterschied zum vorherigen Programm wird der Fehler diesmal abgefangen und der Begriff Uncaught erscheint demnach nicht.
Abbildung 2.25 Fehler abfangen mit »try … catch«
2.5.4 Ausnahmen werfen mit »throw«
Sie können auch eigene Fehlersituationen erzeugen. Bei Eintritt eines solchen Falles kann mit throw eine Ausnahme geworfen werden. Sie profitieren dabei von dem Ablauf der Ausnahmebehandlung, der dafür sorgt, dass das Programm in den catch-Block verzweigt.
In folgendem Beispiel soll der Benutzer eine Zahl eingeben, die größer als 0 ist. Es soll zwei Situationen geben, die als Fehler gelten:
-
Die Eingabe stellt keine gültige Zahl dar.
-
Es wird eine Zahl eingegeben, die zu klein ist.
In beiden Fällen soll eine passende Fehlermeldung ausgegeben werden. Es folgt das Programm:
...
<body>
<p><script>
var wert = parseFloat(prompt("Bitte eine Zahl > 0 eingeben"));
try
{
if(isNaN(wert))
throw "Keine gültige Zahl";
if(wert <= 0)
throw "Zahl zu klein";
document.write(wert);
}
catch(fehlerobjekt)
{
alert(fehlerobjekt);
}
</script></p>
</body></html>
Listing 2.23 Datei »throw.htm«
Falls eine der beiden Situationen eintritt, wird dies jeweils dank der Verzweigung erkannt. Die throw-Anweisung sorgt dafür, dass das Programm unmittelbar in den catch-Block verzweigt. Gleichzeitig wird eine passende Fehlermeldung an den catch-Block übermittelt. Diese Meldung wird ausgegeben. In Abbildung 2.26 sehen Sie die Meldung, die angezeigt wird, falls der Benutzer den Wert –5 eingibt.
Abbildung 2.26 Eigene Ausnahme geworfen
2.5.5 Programm debuggen
In aktuellen Browsern haben Sie normalerweise die Möglichkeit, Ihre Programme zu debuggen, entweder standardmäßig oder über eine Erweiterung. Das Debugging hilft Ihnen dabei, logische Fehler zu finden. Sie können Ihre Programme schrittweise ablaufen lassen und sich die Werte aller Variablen zu beliebigen Zeitpunkten ansehen. Nachfolgend wird das am Beispiel des Browsers Chrome 64 unter Windows 10 gezeigt.
Zur Erläuterung wird ein einfaches Programm in der Datei chrome_debug.htm genutzt, in dem zwei Zahlen addiert werden. Rufen Sie das Programm normal auf, wie in Abbildung 2.27 zu sehen.
Abbildung 2.27 Programm, normaler Ablauf
Zum Einleiten des Debuggings öffnen Sie das Menü des Browsers, über die drei Punkte oben rechts. Wählen Sie darin die Menüpunkte Weitere Tools • Entwicklertools. Auf der rechten Seite werden anschließend die Entwicklertools eingeblendet. Sie bieten viele verschiedene Möglichkeiten. Zum Debugging wählen Sie im oberen Bereich die Registerkarte Sources. Auf der linken Seite können Sie über die Registerkarte Network die aktuelle Datei chrome_debug.htm auswählen. Nach einem Doppelklick wird der Code der Datei auf der rechten Seite eingeblendet, siehe Abbildung 2.28. Vor den einzelnen Zeilen werden Zeilennummern angezeigt.
Abbildung 2.28 Quellcode, mit Zeilennummern
Nach einem Klick auf eine der Zeilennummern wird ein Haltepunkt (engl.: breakpoint) für diese Zeile erzeugt. Ein weiterer Klick entfernt den Haltepunkt wieder. In Abbildung 2.29 sehen Sie, dass Haltepunkte neben den Zeilen erzeugt wurden, in denen die Variablen b und ausgabe ihre Werte erhalten.
Abbildung 2.29 Zwei Haltepunkte gesetzt
Nach dem erneuten Laden des Dokuments läuft das Programm nicht vollständig durch, sondern hält vor Ausführung derjenigen Zeile an, in der der erste Haltepunkt gesetzt wurde. Die Variable a hat zu diesem Zeitpunkt bereits einen Wert erhalten, die anderen Variablen noch nicht.
Ihre Werte können Sie etwas weiter unten rechts in der Registerkarte Watch überwachen. Betätigen Sie zum Einblenden der Variablen die Symbol-Schaltfläche mit dem Pluszeichen, tragen Sie den Namen der gewünschten Variablen ein und betätigen Sie die Taste (¢). In Abbildung 2.30 sehen Sie den Zustand nach dem Anhalten des Programms, nachdem ich zuvor alle vier Variablen des Programms eingeblendet habe.
Den weiteren Ablauf des Programms können Sie über die Symbol-Schaltflächen steuern, die Sie oberhalb des Textes Paused on breakpoint in Abbildung 2.30 sehen:
-
Die erste Symbol-Schaltfläche (Resume Script Execution, auch: Taste (F8)) dient zum Fortsetzen des Programms bis zum nächsten Haltepunkt (falls vorhanden) oder bis zum Ende des Programms.
-
Die zweite Symbol-Schaltfläche (Step over next function call, auch: Taste (F10)) wird zum Überspringen einer Funktion eingesetzt, so dass Sie die Auswirkungen nach dem Durchlauf der gesamten Funktion beobachten können.
-
Die dritte Symbol-Schaltfläche (Step into next function call, auch: Taste (F11)) dient zum Hineinspringen in eine Funktion Ihres Programms. Anschließend können Sie die Auswirkung einzelner Anweisungen innerhalb der Funktion beobachten.
-
Sollten Sie sich innerhalb einer Funktion befinden, dann können Sie mit der vierten Symbol-Schaltfläche (Step out of current function, auch: Tastenkombination (ª)) herausspringen, ohne die Auswirkung der restlichen, einzelnen Anweisungen in der Funktion zu beobachten.
Abbildung 2.30 Programm steht vor Zeile 10
Das Thema Funktionen wird im nächsten Abschnitt ausführlich behandelt. Nach Betätigung der ersten Symbol-Schaltfläche hält das Programm am nächsten Haltepunkt an, siehe Abbildung 2.31.
Abbildung 2.31 Programm steht vor Zeile 12
Bis zu diesem Zeitpunkt haben auch die Variablen b und c Werte erhalten. Nach dem nächsten Fortsetzen läuft das Programm bis zum Ende, und die Ausgabe erscheint. Wie bereits erwähnt: Ein weiterer Klick auf die Zeilennummer entfernt den betreffenden Haltepunkt wieder und Sie können Ihr Programm normal starten.