Debugging
Bei der Entwicklung eines Softwareprojekts geht es nicht nur darum Quellcode zu planen und schreiben sondern auch unweigerlich darum, auftretende Fehler zu beheben. Aber wie gehen wir dabei effektiv vor? Vor allem bei größeren Projekten?
Finden von Fehlern im Quellcode:
Unser Quellcode tut also nicht was er soll. Der erste Schritt das zu beheben, ist die Stelle zu finden, an der der Fehler auftritt. Also: Warum tritt der Fehler auf?
Dafür gibt es verschiedene Möglichkeiten:
Auskommentieren:
Eine der einfachsten Methoden ist es, Code den man als Fehleranfällig vermutet (meißt seit dem letzten Ausführen neu hinzugefügter Code) auszukommentieren um zu entscheiden ob der neu hinzugefügte Abschnitt tatsächlich den Fehler verursacht. Da dieser Ansatz durch vieles Ausführen und Aus/Einkommentieren sehr aufwändig ist, ist es nur selten wirklich effizient.
Zwischenausgaben:
Eine weitere Methode Quellcode nach Fehlern zu untersuchen ist es, vor oder nach verschiedenen Schritten Ausgaben zu tätigen, um den Zustand bestimmter Variablen oder gar eines oder mehrerer gesamter Objekte zu untersuchen und nach Fehlern zu prüfen.
Die beiden eben besprochenen Methoden können immer unabhängig von der IDE durchgeführt werden. Auch sind diese für kleine Projekte schon vollkommen ausreichend. Wenn wir größere Projekte untersuchen wollen, nehmen wir uns die IDE etwas mehr zu hilfe und nutzen den integrierten Debugger (dieses Vorgehen meinen wir dann auch in der Regel wenn wir von Debugging sprechen):
Debugger der IDE:
Wir werden hier ausführlich über die JetBrains IntelliJ und Eclipse reden. In anderen IDEs gibt es auch Debugger, die im groben genauso funktionieren, bei denen aber einige Bedienelemente anders angeordnet sind. Da IntelliJ und Eclipse die meißt genutzten sind, werden im folgenden nur diese beiden erläutert:
IntelliJ:
Um in IntelliJ eine Fehlerquelle zu identifizieren müssen wir einen sogenannten "Breakpoint" setzten. Dieser sorgt dafür, dass das Programm genau an dieser Stelle pausiert, wenn wir es im Debug-Modus starten. Der Breakpoint wird durch einen simplen Linksklick auf die Zeilennummer innerhalb der Klasse erstellt. Das Program hält dann vor der Bearbeitung dieser Zeile an und gewährt uns vollen Einblick in den aktuellen Zustand der verwendeten Variablen, Parameter und Objekte. Entfernen lässt sich ein Breakpoint durch erneutes Linksklicken auf diesen.
Um unser Program jetzt in dem besagten Debugging-Modus auszuführen, gibt es einen zusätzlichen Button direkt neben dem "normalen" ausführen. Dieser führt das Program mit den definierten Eigenschaften (Main Klasse, Parameter, ...) aus, pausiert aber an jedem von uns definierten Breakpoint.
Ein normales Ausführen ignoriert sämtliche Breakpoints und ist daher unwirksam für unsere Zwecke.
Wir erhalten beim Ausführen eine neue Ansicht. In dieser gibt es nun einige Felder und Buttons um uns in dem Debugging-Prozess zu unterstützen:
Zunächst die Buttons (1- 8):
- Führt das Program erneut im Debugging-Modus aus
- Stoppt das Programm
- Springt zum nächsten definierten Breakpoint
- "Step over": Verhindert das untersuchen der als nächstes ausgeführten Zeile, auch wenn es eine Methode ist. D.h. die implementation der Methode wird übersprungen. Der Debugger wird trotzdem in der Methode anhalten, wenn dort Breakpoints vorhanden sind. Auch das kann durch einen sogenannten "Force step over" verhindert werden.
- "Step into": Gewährt Einblick in die Implementierung einer Methode. Das ist insbesondere hilfreich, wenn nicht klar ist, was diese Methode tut oder wie sie implementiert ist, das aber für Zwecke des Debuggings notwendiges Wissen ist. Sind mehrere Methoden vorhanden, so wird gefragt, in welche davon Einblick gewährt werden soll ("Smart Step Into"). Dieses Feature muss gegebenenfalls erst eingerichtet werden.
- "Step out": Springt direkt aus dem aktuellen Codeblock hinaus. Das ist insbesondere dann hilfreich, wenn ein Breakpoint innerhalb eine for- oder while-Schleife ist. Dieser Button überspringt dann alle weiteren Iterationen und hält das Program am nächsten Breakpoint außerhalb der Schleife an.
- Zeigt alle gesetzten Breakpoints in einem gesonderten Fenster an
- Setzt alle Breakpoints auf Stumm und überspringt diese dann. Geeignet wenn ein bestimmer Punkt im Quellcode erst erreicht werden soll, der dann erst Debugged werden soll.
Threads & Variables (9):
Zu diesem Fenster gehören die beiden Flächen unterhalb der Knöpfe.
Links zu sehen sind die Threads des Programms und die darin aktuell ausgeführten Instruktionen. Diese werden geschachtelt angezeigt. In dem gegebenen Beispiel wird also eine Methode "execute" der Klasse "ScoreCommand" ausgeführt, die selbst in einer Methode "runInstance" der Klasse "V4Instance" ausgeführt wird, die wiederum selbst in der main-Methode der Main Klasse steht. Diese Seite wird uns allerdings hier nicht so häufig interessieren, da unsere Programme meißt noch relativ klein sind.
Rechts sehen wir den zunächst wichtigsten Teil:
Sämtliche Variablen, Paramter und Objekte werden hier gesammelt angezeigt.
Durch das öffnen verschiedener Felder mit den Pfeilen, lassen sich alle Einzelheiten eines Objektes anzeigen und somit präzise der aktuelle Zustand dieses analysieren. Dieselben Werte werden auch im Editor an den entsprechenden Stellen angezeigt und lassen sich dort teil mithilfe der "Change Value" Option verändern um so verschiedene Zustände auf richtigkeit zu überprüfen, ohne das Program jedes mal neu starten zu müssen. Sind Werte nicht angezeigt, die eigentlich auch von überprüft werden sollen, so lässst sich an der entsprechenden Position der Initialisierung im Quellcode auch ein "Breakpoint" setzen. Dieser wird dann "Field watchpoint" genannt und ist einzig und allein dafür da, diese Variable im Debugging Menü mit anzuzeigen, also diese Variable zu beobachten.
Console (10): Die Konsole wie wie sie aus der normalen Ausführung des Programms schon kennen.
Eclipse:
Um hier Breakpoints zu setzen muss auf den linken Rand, neben der Zeilennummer ein Rechtsklick getätigt werden und dann "Toggle Breakpoint" ausgewählt werden.
Ansonsten funktionieren Breakpoints analog zu denen in IntelliJ
Um den Debugger zu starten gibt es zwei Möglichkeiten:
- Run -> Debug As -> 1 Java Application (Hierbei kann auch eine Konfiguration eingestellt werden, unter welchen Bedingungen das Program starten soll, so z.B. Parameter, Main Klasse, ...)
- Ist schon eine Konfiguration vorhanden oder wird keine benötigt, so kann auch der Debug-Button gedrückt werden der links neben dem Button zur normalen Ausführung ist
Anschließend erhalten wir mehrere Fenster (Standardgemäß sind diese links/rechts vom Quellcodefenster):
Dieses Fenster befindet sich links des Quellcodefensters und zeigt an, welche Methode in welcher Klasse in welchem Thread gerade ausgeführt wird. Das ist also analog zu dem Threads Fenster in IntelliJ.
Auch auf dieser Seite befinden sich die Knöpfe zum bedienen des Debuggers. Diese sind etwas weiter rechts neben den Ausführen Buttons angeordnet. Zur Verfügung sind:
- Springt zum nächsten Breakpoint
- Pausiert da Program
- Beendet das program
- Step into (analog zu IntelliJ)
- Step over (analog zu IntelliJ)
- Step return (analog zu Step out in IntelliJ)
Diese Fenster befinden sich rechts des Quellcodefensters und zeigen den Zustand und die Breakpoints des Programms an. Auch hier ist es analog zu IntelliJ.
Debugging ist ein sehr großes und umfangreiches Thema, sodass hier jetzt quasi nur die "Basics der Basics" eingeführt wurden. Diese reichen in der Regel auch vollkommen aus. Wer sich gerne mehr einlesen möchte kann sich für IntelliJ hier und für Eclipse hier einen größeren Überblick verschaffen.