Debugging
Kurz: Das systematische Aufspüren und Beheben von Fehlern im Code — in Java meist mit dem Debugger der IDE (Breakpoints, Schritt-für-Schritt-Ausführung, Variablen-Inspektion).
Genauer: Ein Breakpoint pausiert das Programm an einer bestimmten Zeile, sodass man den aktuellen Zustand aller Variablen einsehen kann, statt blind zu raten. Ergänzend helfen Stacktraces bei Exceptions und gezielte System.out.println-Ausgaben, auch wenn Letzteres bei komplexeren Fehlern schnell an Grenzen stößt.
Im Detail
Der systematische Debugging-Ablauf in einer IDE (IntelliJ, Eclipse, VS Code): einen Breakpoint per Klick in die Zeilennummer-Spalte setzen, das Programm im Debug-Modus starten (nicht im normalen Run-Modus), und sobald die markierte Zeile erreicht wird, pausiert die Ausführung dort automatisch. Danach:
- Step Over: nächste Zeile ausführen, ohne in aufgerufene Methoden hineinzuspringen
- Step Into: in eine aufgerufene Methode hineinspringen, um deren Ablauf zu verfolgen
- Step Out: die aktuelle Methode zu Ende laufen lassen und zurück zum Aufrufer springen
- Variablen-Fenster: zeigt live die aktuellen Werte aller sichtbaren Variablen
- Watch-Ausdrücke: eigene Ausdrücke definieren, die bei jedem Halt neu ausgewertet werden (z. B.
liste.size() > 0)
Bedingte Breakpoints (rechte Maustaste auf den Breakpoint) lösen nur aus, wenn eine bestimmte Bedingung erfüllt ist — z. B. nur beim 50. Schleifendurchlauf pausieren, statt bei jedem einzelnen. Ein Stacktrace bei einer nicht abgefangenen Exception listet von oben nach unten den genauen Methodenaufrufpfad, der zum Fehler geführt hat — die OBERSTE Zeile zeigt meist die eigentliche Fehlerursache, weiter unten steht der Kontext, wie man dort hingekommen ist.
Print-Debugging als einfache Alternative
Bevor man einen vollwertigen Debugger einsetzt, greifen viele (besonders Einsteiger) zu gezielten System.out.println()-Ausgaben, um den Programmablauf und Variablenwerte sichtbar zu machen:
System.out.println("DEBUG: x = " + x + ", zustand = " + zustand);Das funktioniert für einfache Fälle, wird aber bei komplexeren Bugs schnell unübersichtlich (viele Ausgaben, die man am Ende wieder entfernen muss) und liefert keine Möglichkeit, den Programmablauf anzuhalten und interaktiv zu untersuchen — dafür ist ein echter Debugger überlegen.
Logging statt println für produktiven Code
Für Code, der auch nach dem Debugging im Betrieb bleiben soll, ist eine Logging-Bibliothek (z. B. SLF4J mit Logback) der bessere Ansatz als System.out.println(): Log-Level (DEBUG, INFO, WARN, ERROR) lassen sich zur Laufzeit ein-/ausschalten, Ausgaben landen automatisch mit Zeitstempel in Dateien statt nur in der Konsole, und Debug-Ausgaben müssen nicht mühsam wieder aus dem Code entfernt werden.
Rubber Duck Debugging
Eine bekannte, nicht-technische Debugging-Technik: das Problem laut (z. B. einer Gummi-Ente auf dem Schreibtisch) Schritt für Schritt erklären. Allein der Zwang, die eigene Logik in vollständigen Sätzen zu formulieren, deckt überraschend oft die Fehlerursache auf, noch bevor man überhaupt einen Debugger startet.
Häufige Bug-Kategorien
Compiler-Fehler (Syntaxfehler, fehlende Semikolons) werden schon vor der Ausführung erkannt und sind meist am leichtesten zu beheben. Laufzeitfehler (Exceptions wie NullPointerException) zeigen sich erst beim Ausführen, liefern aber immerhin einen Stacktrace als Hinweis. Logikfehler (das Programm läuft fehlerfrei durch, liefert aber ein falsches Ergebnis) sind am schwierigsten zu finden, weil kein Fehler/keine Exception auf das Problem hinweist — hier ist gezieltes Debugging mit Breakpoints an kritischen Stellen meist unumgänglich.
Siehe auch: Errors, Exceptions