Debugging mit Eclipsev.1.0.0
Demonstrationsumgebung: Eclipse Java EE IDE for Web Developers. Version: Kepler Service Release 1
Einführung
Eine effektive Fehlererkennung und -behebung während der Programmierung stellen Grundvoraussetzungen für eine zeitsparende und zufriedenstellende Softwareentwicklung dar. Eclipse bietet mit seiner Debug Oberfläche hierzu umfangreiche Möglichkeiten, von denen hier einige einführend dargestellt werden sollen.
Obwohl die Screenshots der o.a. Version entstammen, sollte das
vorliegende Tutorial für alle zur Java-Entwicklung
geeigneten Eclipse-Varianten gültig sein.
Um den Text
nicht unnötig zu verkomplizieren, werden einige englische
Fachbegriffe übernommen oder, sprachlich nicht ganz
korrekt, eingedeutsch, sodass an Stellen, an denen es geboten
erscheint z.B. von debuggen oder debugged die
Rede sein wird.
Debug-Oberfläche
Starten Sie Eclipse und erstellen Sie ein neues Java-Projekt
durch Auswahl von New → Java Project. Erzeugen Sie
hierin eine Klasse DebugBsp1
mit dem folgenden
Quelltext:
import java.awt.Point; public class DebugBsp1 { static Point point; public static void main(String[] args) { point = new Point(100, 50); printPoint(point); } private static void printPoint(final Point point) { System.out.print(point.x); System.out.print(", " + point.y); } }
In der Klasse wird ein Point
-Objekt deklariert, in
der main
-Methode initialisiert und seine x- und
y-Werte über die Methode printPoint()
nacheinander auf der Konsole ausgegeben.
Starten Sie den Debug-Modus durch Klicken auf das Icon mit der Wanze oder durch Auswahl von Run → Debug As → Java Application im Menu.
Es öffnet sich die Debug Perspective wie im
folgenden Screenshot abgebildet. Sollte das nicht der Fall sein,
so öffnen Sie die Ansicht durch Auswahl von Window
→ Open Perspective → Other… → Debug
in der Menuleiste.
Die durch Pfeile gekennzeichneten Bereiche bezeichnen die einzelnen Teilfenster (Views), deren genaue Funktionen im Laufe des Tutorials verdeutlicht werden. Sie können unabhängig voneinander minimiert und maximiert werden und viele von ihnen besitzen eigene Werkzeugleisten. Darüber hinaus lassen sie sich fast beliebig anordnen, indem man auf den Kopf mit dem Titel eines Teilfensters klickt und es an eine andere Stelle zieht. Unter dem Menupunkt Window → Save Perspective As… lässt sich die bevorzugte Einstellung bei Bedarf speichern.
- Debug Fenster - In ihm werden die ausgeführten Threads und deren Stack dargestellt.
- Quelltext Fenster - Der Editor, in dem während des Debuggens der Quelltext angezeigt und ggf. auch bearbeitet werden kann.
- Outline Fenster - Es dient dem Überblick bei umfangreichen Quelltexten und erfasst die wesentlichen Gliederungseinheiten des aktuell im Editor angezeigten Textes. Durch Anklicken kann zur jeweiligen Stelle im Quelltext gesprungen werden. Für das vorliegende Tutorial wird diese Ansicht nicht benötigt und kann bedenkenlos geschlossen werden. Sollte sie danach doch einmal benötigt werden, so kann sie durch Auswahl von Window → Show View → Outline wieder geöffnet werden.
- Variablen und Breakpoints Fenster - Hier werden die Bezeichner und Werte von Variablen, sowie aktivierte und deaktivierte Breakpoints angezeigt. Beide Fenster sind momentan leer.
- Console Fenster - Die Konsole wie aus der Java Perspective bekannt. Hier werden Ausgaben des Programms über den Standard-Ausgabe-Strom oder den Fehlerstrom ausgegeben, bzw. ggf. auch Eingaben des Standard-Eingabe-Stroms vom Programm entgegengenommen.
Beim Durchlaufen des Programms ändern sich die
Einträge in den erwähnten Feldern entsprechend und
nachdem es erfolgreich beendet wurde, wird in der Konsole die
erwartete Ausgabe 100, 50
angezeigt.
Breakpoints
Breakpoints sind frei zu wählende Punkte, an denen die
Programmausführung beim Debugging anhalten soll.
Fügen Sie in Ihrem Programm einen Breakpoint vor dem
Methodenaufruf
printPoint(point);
ein, indem Sie im Editor auf die Leiste vor der Zeilennummerierung doppelt klicken oder dort im Kontextmenu Toggle Breakpoint wählen.
Starten Sie den Debug-Vorgang durch Klicken auf das Wanzen-Icon erneut. Das Ergebnis ist im folgenden Screenshot ersichtlich.
Das Breakpoint-Icon im Editor hat einen kleinen blauen Pfeil erhalten und die Zeile selbst ist grün unterlegt worden. Der Programmablauf wurde hier vor dem Ausführen der Zeile unterbrochen und die Methode somit noch nicht aufgerufen. Die Konsole ist dementsrechend leer.
Navigation
Mit Hilfe der Navigationsleiste lässt sich das Programm nun von hier aus Zeile für Zeile weiter ausführen. Die Navigationsleiste befindet sich als Teil oben in der Hauptwerkzeugleiste oder, falls aktiviert, als Debug Tool Bar im Debug-Fenster. Die für uns wichtigsten Icons bezeichnen die folgenden Buttons. Sie werden dynamisch aktiviert und sind somit nicht immer sichtbar:
- Step over
- Es wird zur nächsten Quelltextzeile gesprungen. Bei einem Methodenaufruf, wird diese zwar ausgeführt, der Zeiger springt jedoch nicht in die Methode hinein, sondern behandelt sie quasi als Black Box.
- Step into
- Die aktuelle Quelltextzeile wird rekursiv verfolgt. Bei einem Methodenaufruf springt der Zeiger in die Methode hinein.
- Step return
- Der Zeiger springt nach dem Betreten einer Methode zu deren Ende.
- Drop To Frame
- Der Zeiger springt zurück zum Beginn des aktuellen stack frame.
- Use Step Filters
- Hiermit lässt sich der Debugging-Vorgang filtern. Näheres hierzu findet sich weiter unten.
- Resume
- Die Programmausführung wird wieder aufgenommen und das Programm bis zum nächsten Breakpoint oder bis zum Ende ausgeführt.
- Terminate
- Abbruch der Programmausführung.
Klicken Sie nun auf Step Into . Die erste Zeile
innerhalb der Methode printPoint()
wird grün
unterlegt und erhält den kleinen blauen Pfeil vorne. Der
Debug-Zeiger ist in die Methode gesprungen und verharrt dort.
Klicken Sie auf Step Over und der Zeiger
markiert die nächste Zeile der Methode ohne in die Methode
print()
des PrintStream System.out
zu
wechseln.
Auch hier wurde der Programmablauf wieder vor dem Ausführen der Zeile unterbrochen. Die letzte Konsolen-Ausgabe war somit lediglich der x-Wert des Point-Objektes.
Aktivieren Sie nun das Breakpoint-Fenster, in dem die genaue Position des Breakpoints (nicht des Debug-Zeigers) angezeigt wird, sowie eine Checkbox, mit der der Breakpoint aktiviert und deaktiviert werden kann. Beides ist insbesondere dann wichtig, wenn ein Programmablauf durch das Setzen mehrerer Breakpoints nachvollzogen werden soll.
Wenn Sie nun auf das kleine grüne Dreieck mit dem gelben Balken davor in der Hauptwerkzeugleiste klicken, können Sie das Programm zu Ende ausführen. Sollte einmal ein Fehler auftreten oder das Programm nicht sauber zu Ende zu führen sein, so lässt sich die Programmausführung durch das rote Quadrat entweder in der Hauptwerkzeugleiste oder in derjenigen der Konsole beenden.
Beobachten von Variablenbelegungen zur Laufzeit
Führen Sie den Programmablauf zu Ende und löschen
Sie den bisherigen Breakpoint durch doppeltes Klicken auf den
Punkt oder indem Sie auf ihn rechts klicken und aus dem
Kontextmenu Toggle Breakpoint wählen. Setzen Sie
anschließend die folgenden zwei Zeilen ans Ende der Methode
printPoint()
,
point.x = 75;
System.out.println("\n" + point.x + ", " +
point.y);
sodass sie wie folgt aussieht:
private static void printPoint(final Point point) { System.out.print(point.x); System.out.print(", " + point.y); point.x = 75; System.out.println("\n" + point.x + ", " + point.y); }
Setzen Sie vor die beiden hinzugefügten Zeilen jeweils einen Breakpoint und starten Sie den Debug-Modus.
Die Programmausführung hält vor der ersten Zeile mit
Breakpoint an. Im Variablen-Fenster sind die erwarteten Werte x=100
und y=50
zu sehen, da die Zuweisung von point.x=75
noch nicht erfolgt ist. Führt man das Programm weiter aus,
so unterbricht es vor der nächsten Zeile und in der
Variablenansicht ist der neu übergebene x-Wert zu sehen.
Das Variablen-Fenster zeigt also den jeweiligen Wert der im
Programm verwendeten Variablen.
Ändern von Variablen zur Laufzeit
Variablenwerte können zur Debug-Zeit auch geändert
werden, um das Verhalten eines Programms zu
überprüfen. Klicken Sie hierzu im Variablenfenster auf
den Wert der x-Eigenschaft, ändern Sie diesen in 80
ab und speichern Sie die Änderung durch Cmd + S
oder Strg + S ab. Führen Sie das Programm weiter
aus. Sie sehen in der Konsolenansicht, dass der geänderte
Wert übernommen wurde.
Eine weitere Option besteht darin, zur Debugzeit ganze
Ausdrücke hinzuzufügen, die dann im weiteren
Programmverlauf verfolgt werden können.
Führen Sie
das Programm erneut bis zum ersten Breakpoint aus und wechseln
Sie dann ins Variablen-Fenster. Klicken Sie dort auf Add new
expression und geben Sie System.currentTimeMillis()
ein. Der Ausdruck gibt im rechten Feld die Differenz in
Millisekunden zwischen dem jetzigen Zeitpunkt und dem 1.1.1970
aus. Führen Sie das Programm durch Step Over
weiter aus und beobachten Sie, wie sich die Ausgabe ändert.
Führen Sie einen Rechtsklick auf den Ausdruck aus und wählen Sie dort Reevaluate Watch Expression. Der Ausdruck wird hierdurch erneut ausgewertet und die Ausgabe erneuert. Die Auswahl von Disable deaktiviert hingegen den Ausdruck. Bei der weiteren Programmausführung ändert sich die Ausgabe nun nicht mehr.
Methoden-Breakpoints
Um die Arbeitsweise von Methoden zu prüfen, kennt der Eclipse-Debugger Methoden-Breakpoints. Sie unterbrechen die Ausführung einer Methode sofort nach deren Betreten, also noch vor der Auswertung der ersten Zeile, direkt vor dem Verlassen des Methodenkörpers, also nach Auswertung der letzten Zeile oder aber an beiden Punkten.
Beenden Sie das noch laufenden Programm falls noch nicht
geschehen und entfernen Sie alle Breakpoints. Dies kann durch
Klicken auf das Icon mit dem doppelten X im
Breakpoint-Fenster geschehen.
Setzen Sie einen neuen
Breakpoint vor die Zeile mit der Methodensignatur von printPoint()
.
Das kleine Icon des Breakpoints weist nun einen dünnen
schwarzen Pfeil nach rechts aus. Dies entspricht dem Standard
eines Methoden-Breakpoints und zeigt, dass er nur beim Betreten
der Methode aktiv ist.
Schauen Sie sich das Breakpoint-Fenster an und expandieren Sie es ggf. in die Breite bis die untere Leiste vollständig sichtbar ist.
Sie finden zwei Checkboxen am unteren Rand im rechten Bereich,
die mit Entry und Exit bezeichnet sind. Wenn
Sie den eingetragnen Breakpoint markieren und die Checkbox Exit
aktivieren, sehen Sie, wie das kleine Breakpoint-Icon im Editor
noch einen Pfeil nach links erhält.
Ändern Sie die
Einstellungen des Breakpoints versuchsweise und kontrollieren
Sie die entsprechenden Variablenbelegungen zu Beginn und am Ende
der Methode durch wiederholtes Ausführen des
Debugging-Vorgangs.
Links neben den erwähnten Einstellungen zu den
Methoden-Breakpoints findet sich eine praktische Einrichtung mit
der Bezeichnung Hit Count, die es ermöglicht,
wiederholte Ausführungen, etwa bei
Schleifendurchläufen, etc., genauer zu kontrollieren.
Ändern
Sie die Methode printPoint()
wie folgt ab:
private static void printPoint(final Point point) { for (int i = 0; i < 5; i++) { point.x += i * 10; System.out.println(point.x + ", " + point.y); } }
Setzen Sie einen Breakpoint vor die Ausgabezeile System.out…
,
aktivieren Sie in der Breakpoint-View die Checkbox Hit Count
und tragen Sie in das Textfeld daneben eine 4
ein.
Sie erreichen so, dass der Breakpoint erst beim vierten
Schleifendurchlauf aktiv wird, die Programmausführung also
vor der vierten Ausgabe anhält.
Exception Breakpoints
Die bislang gezeigten Methoden können erfolgreich
eingesetzt werden, wenn ein Programm prinzipiell lauffähig
ist und keine Exceptions oder Errors eintreten. Schwierig wird
es jedoch manchmal, wenn Fehlfunktionen vorliegen, da das
Auslösen und Abfangen von Exceptions u.U. an völlig
verschiedenen Programmstellen erfolgen können.
Der
Eclipse-Debugger kennt hierfür den Exception-Breakpoint,
der nicht an einer vorgegebenen Quelltextposition gesetzt wird,
sondern vielmehr auf einen vorgegebenen Typ von Exception
reagiert.
Erzeugen Sie zu diesem Zweck eine beabsichtigte NumberFormatException
und ändern Sie hierzu die Methode ein weiteres Mal durch
Eingabe eines unzulässigen Integer
-Wertes ab:
private static void printPoint(final Point point) { point.x = new Integer("achtzig"); System.out.println(point.x + ", " + point.y); }
Gehen Sie im Debug-Modus ins Breakpoint-Fenster und klicken Sie in der dortigen Werkzeugleiste auf den Button zur Erzeugung eines Exception-Breakpoints . Es öffnet sich ein Dialog zu dessen Spezifizierung:
In das obere Textfeld können die Anfangsbuchstaben der
gesuchten Exception eingetragen werden. Dies kann auch mit Hilfe
der gängigen Wildcards erfolgen.
Geben Sie also z.B. Num*Fo*Exc
ein und markieren Sie NumberFormatException
. Es
wird ein Exception-Breakpoint erzeugt, dessen Gültigkeit
Sie noch dadurch einschränken können, dass Sie die
Checkboxen für abgefangene und/oder nicht abgefangene
Exceptions wunschgemäß aktivieren.
Debugged man nun das Programm, so öffnet in unserem Fall
der Eclipse-eigene Recompiler die Klasse Integer.class
und zeigt im Quelltext die Stelle, an der die Exception erzeugt
wird.
Es ist wichtig zu registrieren, dass der Debugger hier die Stelle zeigt, an der die Exception erzeugt und nicht nur abgefangen wird. Dies kann einen wichtigen Hinweis auf die jeweilige Fehlerquelle geben.
Stack-Frame
An dieser Stelle soll zumindest kurz auf den Ausführungs-Stack hingewiesen werden. Er ist im Debug-Fenster zu sehen und gibt u.a. Auskunft über die Ausführungsreihenfolge von Methoden. Hierbei ist oben der jüngste Aufruf abgebildet. Der Stack gibt einen oft hilfreichen Überblick über den Verlauf von Methodenaufrufen, ohne dass erst mühsam mit Stop Over oder Step Into navigiert werden muss.
Es ist zu sehen, dass ganz unten mit main()
der
Methodenaufruf zu finden ist, in dem printPoint()
aufgerufen wurde. Von dort aus wurden der Konstruktor von Integer
und schließlich die Methode parseInt()
angesprochen, in der die erwartete NumberFormatException
geworfen wurde.
Durch einen Doppelklick auf den Eintrag
gelangt man zum jeweiligen Quelltext, sofern dieser in den
Eigenschaften des Projektes eingetragen ist.
Debugging mit Stepfilter
Stepfilter dienen dazu, bei der Verfolgung des Programmablaufes
beim Debugging offensichtlich unnötige
Untersuchungsschritte zu überspringen. Das Filtern kann
durch das entsrechende Icon in der Debug-Werkzeugleiste ein- und
ausgeschaltet werden, sollte jedoch vor seiner Nutzung in den
Programmeinstellungen konfiguriert werden.
Gehen Sie hierzu
in die Eclipse-Einstellungen unter Preferences → Java → Debug → Step Filtering (1).
Hier können Sie für das Debugging von gängigen
Java-Programmen getrost alles aktivieren (2). Hierdurch wird das
üblicherweise unnötige Absteigen in die Klassen der
hier angegebenen Packages verhindert. Mit (3) sind hier Buttons
markiert, mit denen Klassen und Packages zur Auswahl
hinzugefügt, sowie eigene Filter definiert werden
können. Dies ist z.B. dann sinnvoll, wenn Fremdpakete, die
nicht verändert werden können oder sollen beim
Debugging unberücksichtigt bleiben sollen.
Üblicherweise
ist es auch sinnvoll einfache Getter- und Setter-Methoden mit in
den Filter aufzunehmen (4). Der Debugger ignoriert dann
Methoden, die lediglich einen Wert zurückgeben oder setzen.
Erzeugen Sie eine neue Klasse DebugBsp2
mit dem
folgenden Quelltext.
public class DebugBsp2 { public static void main(String[] args) { Person paul = new Person(); paul.setName("Paul"); paul.setAlter(56); System.out.println(paul.toString()); } } class Person { String name; int alter; public String getName() { return name; } public void setName(String n) { name = n; } public int getAlter() { return alter; } public void setAlter(int a) { alter = a; } public String toString() { return name + ": " + Integer.toString(alter, 10); } }
Deaktivieren Sie zunächst alle Auswahlmöglichkeiten
der Stepfilter-Einstellungen. Dies kann am einfachsten durch
Klicken auf Restore Defaults geschehen. Speichern
Sie die Einstellungen und aktivieren Sie dann in der
Debug-Ansicht des Hauptfensters den Button zur Anwendung der
Stepfilter . Setzen Sie einen
Breakpoint vor die erste Zeile im Körper der main()
-Methode,
also vor Person paul = new Person();
und starten Sie den Debug-Vorgang des Programms. Die Ausführung wird am Breakpoint unterbrochen. Klicken Sie mehrfach Step Into und beobachten Sie, wie der Abstieg schrittweise in die Tiefen der Java-Core-Klassen erfolgt.
Beenden Sie das Programm und aktivieren Sie nun in den Stepfilter-Einstellungen Use Step Filters, java.*, sun.* und java.lang.ClassLoader und debuggen Sie erneut.
Verwenden Sie Step Into und beobachten Sie wie der
Zeiger sofort zur Klassendeklaration von Person
und
anschließend in die Setter-Methode springt.
Wenn Sie nun
in den Filter-Einstellungen noch einen Haken vor Filter
Simple Setters machen, findet auch kein Abstieg mehr in
solche einfache Setter-Methoden statt.
Bedingungsabhängige Breakpoints
Die Aktivierung eines Breakpoints kann auch von einer Bedingung
abhängig gemacht werden. Um dies zu demonstrieren,
deaktivieren Sie den Stepfilter und entfernen oder deaktivieren
Sie zunächst alle Breakpoints. Setzen Sie dann einen
Methoden-Breakpoint vor die Methode setAlter()
.
Öffnen Sie nun das Breakpointfenster und aktivieren und
markieren Sie den Methoden-Breakpoint. Selektieren Sie die
Checkbox Conditional und den Radio-Button Suspend
when 'true'. Geben Sie in das darunterliegende Feld a==56
ein. Beachten Sie, dass Sie hier den Wert eintragen, den Sie in
main()
der Methode übergeben. Wenn Sie den
Wert hier geändert haben, so müssen Sie die Bedingung
entsprechend anpassen.
Speichern Sie den Eintrag ab und debuggen Sie das Programm. Sie
werden feststellen, dass der Breakpoint aktiv ist und nach
Unterbrechung des Programmablaufs vor Betreten von setAlter()
durch Step Into in die Methode abgestiegen werden kann.
Beenden
Sie den Programmlauf, ändern Sie den Wert für a
im Bedingungsfeld des Breakpoint-Fensters in einen beliebigen
anderen Wert oder geben Sie a!=56
ein, und Sie
werden beim erneuten Debugging feststellen, dass der Breakpoint
nun nicht mehr aktiv ist.
Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.