Fünf Wege der Fehlersuche in Java (original) (raw)

Bitte beachten: dieser Artikel wurde mit freundlicher Genehmigung von Zviki Cohen aus seinem Blog übernommen und ins Deutsche übersetzt.

Oft steht man vor dem Problem, sich in fremden Sourcecode einarbeiten zu müssen. Meistens ist dieser auch noch spärlich dokumentiert und nicht selten hat man nur einen Teil davon vorliegen und muss den Rest dekompilieren. Diesem anstrengenden Prozess ist jeder ausgesetzt, der fremdem Code über den Weg läuft, sei es durch Wartungsarbeiten oder durch Benutzung projektfremder Frameworks und Libraries von Drittanbietern. Stellen Sie sich eine Situation vor, in der Sie das Interface eines Event-Handlers implementieren müssen und unsicher sind, welches Event wann ausgelöst wird und welche Argumente Sie von diesem erhalten.

Meiner Erfahrung nach ist es einfacher, den Code zur Laufzeit nachzuverfolgen, insbesondere wenn man vor nicht-trivialen Problemstellungen steht. Beispiel: Von einem Interface existieren mehrere Implementierungen und wir wollen wissen, welches zur Laufzeit verwendet wird.

In diesem Artikel möchte ich die (mir bekannten) Wege aufzeigen, Java-Code zur Laufzeit nachzuverfolgen. Die Tipps und Beispiele setzen voraus, daß Sie wissen, wie Eclipse verwendet wird, aber die meisten davon können auch in anderen IDEs nachvollzogen werden. Ich werde manche der genannten Techniken in späteren Artikeln aufgreifen und detaillierter darstellen.

1. Das Basisverfahren: Breakpoints und Schritt-für-Schritt-Ausführung

Beginnen wir mit der einfachsten Möglichkeit: setzen Sie Breakpoints und verfolgen Sie jeden einzelnen Schritt der Anwendung nach.

Vorteile:

Nachteile:

Dieses Verfahren lohnt sich wenn…
… Sie eine schnelle und einfache Möglichkeit suchen, Sie den gesamten Sourcecode vorliegen haben, wissen, wo Sie die Anwendung unterbrechen müssen oder ausführliche Anwendungsinformationen (Argumente, lokale Variablen etc.) zu einem bestimmten Zeitpunkt während der Laufzeit der Anwendung benötigen.

Mein Tipp: Auch wenn Sie nicht alle Sourcen haben können Sie die Anwendung unterbrechen wenn eine Methode oder Klasse aufgerufen wird, indem Sie Breakpoints an der Methode, bzw. beim Laden der Klasse, setzen.

2. Die Grundlagen: Debugnachrichten

Wir fahren fort mit der Ausgabe von Debugnachrichten. Der einfachste Weg ist es, System.out.println statements zu verwenden, um Nachrichten auf die Standardausgabe zu schreiben. Fortgeschrittener ist es, einen Loggingmechanismus zu verwenden, wie beispielsweise das Logging im JDK, dem Apache Commons Logging oder den Klassiker Log4j.

Vorteile:

Nachteile:

Dieses Verfahren lohnt sich wenn…
… Sie den Sourcecode vorliegen haben und eine Ahnung davon haben, wonach Sie suchen müssen. Dieses Verfahren eignet sich für das Debugging von Event-Handlern und seine hohe Ausführungsgeschwindigkeit macht sich dann auch während der Nachverfolgung komplexer Abläufe positiv bemerkbar.

Mein Tipp: Erstellen Sie eine generische Methode die die momentan ausgeführte Methode ausgibt. Der beste, mir bekannte, Weg ist, den Stacktrace auszugeben. Der letzte Eintrag auf dem Aufrufstack ist die generische Methode, die die Debugausgabe vornimmt. Der Eintrag davor ist die aufrufende Methode, also die Methode die den Entwickler interessiert. Den Stacktrace bekommen Sie über:

3. Der Aufsteiger: Dynamic Proxy

Eine Verbesserung gegenüber einfachen Debugausgaben ist der Dynamic Proxy. Dieses spezielle Feature der Java Reflection „sitzt vor“ der betroffenen Klasse und fängt alle Aufrufe mittels eines Interfaces ab. Wenn Sie einen Aufruf erst einmal abgefangen haben, können Sie Debugausgaben vornehmen und alle gewünschten Informationen ausgeben.

Vorteile:

Nachteile:

Dieses Verfahren lohnt sich wenn…
… Sie eine sehr gute Lösung für Event-Handler suchen. Sie können einen Dummy-Event mit einem generischen Proxy innerhalb von Sekunden aufsetzen und die Abfolge von Events nachverfolgen. Das ist die einfachste und schnellste Methode, wenn es um die Nachverfolgung von Events geht.

Mein Tipp: Auf Sun’s Website finden Sie Code für einen generischen Dynamic Proxy. Das Beispiel ist ganz gut, allerdings implementiert es nur das unmittelbar implementierte Interface der durch den Proxy gewrappten Klasse. Es implementiert keine seiner weiteren implementierten Interfaces. Das kann leicht behoben werden, indem die Liste der implementierten Interfaces traversiert und alle Interfaces aufgenommen werden. In einem späteren Artikel werde ich eine bessere Version beschreiben.

4. Die rohe Gewalt: Run-time Profiler

Profiler sind mächtige Tools, die alle Aufrufe innerhalb des Systems durch spezielle „Hooks“ nachverfolgen. Mit dieser Methode schiessen Sie allerdings mit Kanonen auf Spatzen.

Vorteile:

Nachteile:

Dieses Verfahren lohnt sich wenn…
… Sie ein Gesamtbild für eine sehr spezifische Operation benötigen, Sie also einen sehr kurzen Anwendungsfall untersuchen möchten.

Mein Tipp: Gute Profiler kosten um die US$ 500,00 pro Entwicklerlizenz. Die kostenlosen Profiler bieten hingegen nur rudimentäre Funktionen an. Meiner Meinung nach ist das Beste Open-Source-Tool für diesen Job die Eclipse Testing and Performance Tools Platform (TPTP). Ich habe sehr gute Erfahrungen mit diesem Tool gemacht und kann es nur empfehlen. Die TPTP stellt beispielsweise den Ablauf des Trace anhand eines Sequenzdiagramms dar, was das Verständnis von Ausführungspfaden erheblich vereinfacht. TPTP gehörte zu meinen Favoriten bis ich auf den Mac gewechselt bin und mit Inkompatibilitäten zu kämpfen hatte (vielleicht behebe ich diese einmal, wenn ich die Zeit dafür finde).

5. Das neue Zeitalter: Aspekte

Die Aspektorientierte Programmierung (AOP) ist ein nicht-triviales Verfahren. Ohne in die Details von Aspekten zu gehen schauen wir uns an, was unter dem Strich bleibt: es ist eine schnelle und einfache Methode, den Ablauf des Codes „abzufangen“. Sie können selektiv „Hooks“ um Methoden, Konstruktoren, Zugriff auf Felder etc. herum setzen, ohne den Originalcode zu modifizieren. In diesen „Hooks“ können Sie dann Debugausgaben vornehmen.

Vorteile:

Nachteile:

Dieses Verfahren lohnt sich wenn…
… Sie den Ablauf einer Anwendung nachverfolgen möchten, dessen Code Sie rekompilieren können.

Mein Tipp: Benutzen Sie es. AOP ist immer noch weit davon entfernt, ein Standardverfahren zu sein und es ist unsicher, wann es zu einem wird. Ohne seine Qualitäten und Features zu diskutieren kann man getrost davon ausgehen, daß die meisten Entwickler innerhalb von einer Stunde mit einem AOP-basierten Logging loslegen können – die exakten Schritte dafür werde ich in einem späteren Artikel darstellen. Denen die Eclipse verwenden empfehle ich die Aspect Java Development Tools (AJDT).

Schlussfolgerung

Welche Methode sollten Sie verwenden? Ich unterscheide hier zwei verschiedene Fälle: die komplette Ausführung einer kurzen Codesequenz nachverfolgen und das nachverfolgen von Events zu/von einer definierten Klasse oder Anwendungsschicht.

Nachverfolgen der kompletten Ausführung:

  1. Ein Profiler stellt den einfachsten Weg dar. Meiner Meinung nach sollte jeder Entwickler in der Lage sein, einen Profiler zu verwenden und einen in seinem Arsenal bereitliegen haben.
  2. Die nächste Wahl ist AOP. Das ist nur die zweite Wahl, weil das Aufsetzen eines Profilers einfacher ist wenn es um JARs geht. Wie auch immer, AOP gibt uns mehr Kontrolle, beispielsweise in der Ausgabe von Methodenargumenten.
  3. Nehmen Sie Debugausgaben vor.

Nachverfolgen von Events:

  1. Benutzen Sie AOP. Fangen Sie selektiv nur die für Sie interessanten Aufrufe ab.
  2. Benutzen Sie einen Dynamic Proxy. Das ähnelt dem Verwenden von Debugausgaben, aber auf eine elegantere Art und Weise. AOP mag anfangs komplex erscheinen, das aber nur zu anfangs. Auf lange Sicht würde ich AOP verwenden.
  3. Nehmen Sie Debugausgaben vor.
  4. Benutzen Sie Breakpoints.

Wie Sie sehen bin ich, in diesem Kontext, kein großer Fan von Breakpoints. Breakpoints eignen sich für das Debugging von Code, aber nicht, um die Ausführung nachzuverfolgen. Dafür gibt es weitaus effizientere Wege.

Update: einige Leser haben mich darauf hingewiesen, daß AspectJ auch für JARs verwendet werden kann. Ich werde dem in einem späteren Artikel nachgehen.

Weiterführende Literatur

Laut dem bekannten Dr. Dobb’s Journal findet sich die beste Beschreibung der Dynamic Proxies in Java Reflection in Action.

Eine sehr gute Einführung in das Standardframework für die Aspektorientierte Programmierung findet sich in Aspektorientierte Programmierung mit AspectJ 5. Einsteigen in AspectJ und AOP..

Weitere Empfehlungen: