Debugging mit Eclipsev.1.0.0

Demonstrationsumgebung: Eclipse Java EE IDE for Web Developers. Version: Kepler Service Release 1

Eclipse bietet mit der Debug-Perspective umfangreiche Möglichkeiten zur Fehlererkennung während der Entwicklung von Applications, Applets, Plugins, etc. Das Tutorial gibt einen Überblick über einige zentrale Funktionen des Debuggings.

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.

Aufruf der Debug-Ansicht


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 Debug-Ansicht im Überblick

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.

  1. Debug Fenster - In ihm werden die ausgeführten Threads und deren Stack dargestellt.
  2. Quelltext Fenster - Der Editor, in dem während des Debuggens der Quelltext angezeigt und ggf. auch bearbeitet werden kann.
  3. 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.
  4. Variablen und Breakpoints Fenster - Hier werden die Bezeichner und Werte von Variablen, sowie aktivierte und deaktivierte Breakpoints angezeigt. Beide Fenster sind momentan leer.
  5. 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.

Breakpoint vor Methodenaufruf

Starten Sie den Debug-Vorgang durch Klicken auf das Wanzen-Icon erneut. Das Ergebnis ist im folgenden Screenshot ersichtlich.

DebugBsp1 mit Breakpoint

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.

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.

Debug Perspective

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.

Breakpointfenster

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.

Zwei Breakpoints

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.

Neue Variablenbelegung

Ä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.

Expression

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.

Expression unterdrückt

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.

Methoden-Breakpoint

Schauen Sie sich das Breakpoint-Fenster an und expandieren Sie es ggf. in die Breite bis die untere Leiste vollständig sichtbar ist.

Breakpoint-Fenster

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:

Exception-Breakpoint-Dialog

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.

Exception-Breakpoint-Dialog

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.

Stack-Fenster

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.

Exception-Breakpoint-Dialog

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();

Exception-Breakpoint-Dialog

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.

Exception-Breakpoint-Dialog

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().

Methoden-Breakpoint vor 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.

Methoden-Breakpoint mit Condition

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.