- 1 Section
- 7 Lessons
- unbegrenzt
Tools & Techniken
Während man einfache Fehler noch mit print() und genauem Hinschauen findet, braucht man bei größeren Projekten gezielte Werkzeuge.
Ein Debugger erlaubt es, den Code Zeile für Zeile zu durchlaufen und genau zu sehen, wie Variablen sich verändern.
Dadurch wird Debugging vom „Raten“ zum gezielten Beobachten.
1. Überblick
| Werkzeug | Wofür genau? | Wann einsetzen? | Ergebnis |
|---|---|---|---|
| Debugger | Code anhalten, schrittweise ausführen, Werte live prüfen | Wenn der Fehler „nur manchmal“ auftritt oder der Ablauf unklar ist | Du siehst Zeile, Werte, Zustand genau dort, wo’s schiefgeht |
| Breakpoints | Pausepunkte setzen (auch bedingt / als Logpoint) | Vor riskanten Ausdrücken (Division, Indexzugriff, Nullzugriff…) | Der Ablauf hält genau dort. Mit Bedingungen sparst du Zeit |
| Watch Expressions | Einzelne Variablen/Ausdrücke ständig beobachten | Wenn du Hypothesen prüfen willst („ist denominator wirklich 0?“) | Du siehst Live-Werte ohne zusätzlichen Code |
| Call-Stack | Aufrufkette (wer hat wen aufgerufen?) | Wenn unklar ist, woher ein fehlerhafter Aufruf kommt | Du findest die eigentliche Ursache (nicht nur das Symptom) |
| Logging | Laufzeitverhalten mitprotokollieren (Level, Struktur) | Wenn Debugger nicht geht (Prod/Server) oder zum Nachvollziehen | Du hast kontextreiche Spuren (Daten, Pfad, Zeit) |
2. Debugger – Schrittsteuerung in der Praxis
2.1 Start & Steuerung
Startpunkt setzen: Breakpoint direkt vor der riskanten Zeile.
Step Over (F10): Nächste Zeile ausführen, ohne in Funktionen reinzugehen.
Step Into (F11): In die aufgerufene Funktion springen.
Step Out (Shift+F11): Aus aktueller Funktion heraus zurückkehren.
Beispiel (Python) – unser Durchschnittsfall mit potenzieller Division:
def average(a, b, c):
return (a + b + c) / c # Breakpoint hier setzen
print(average(5, 10, 15)) # ok
print(average(5, 10, 0)) # Problemfall
Ziel im Debugger:
Beim ersten Call siehst du
c=15, Ergebnis korrekt.Beim zweiten Call hält der Debugger an – vor der Division. Du prüfst
c=0→ Ursache klar.
2.2 Bedingte Breakpoints (Conditional Breakpoints)
Halte nur dann an, wenn es relevant ist—spart Zeit bei Schleifen/mehreren Aufrufen.
Bedingung:
c == 0Effekt: Der Debugger stoppt nur beim Problemfall.
2.3 Logpoints (ohne Anhalten)
Statt zu stoppen, loggt der Debugger eine Nachricht – ohne Code zu ändern.
Nachricht z. B.:
c={c}, expr={(a+b+c)/max(c,1)}Gut, wenn du Ausgaben sehen willst, aber nicht immer pausieren.
3. Breakpoints – präzise einsetzen
3.1 Wo genau hin?
Vor gefährlichen Operationen: Division, Indexzugriff, Typumwandlung, I/O, Netzwerk.
Vor/innerhalb Bedingungen:
if,while(z. B. falsche Logikzweige).
3.2 Arten von Breakpoints
Line Breakpoint: Standard – an einer Zeile anhalten.
Conditional: Nur wenn
c == 0o. ä.Hit Count: Erst nach x-tem Durchlauf stoppen (nützlich bei Schleifen).
Logpoint: Nur Nachricht schreiben (kein Halt).
3.3 Minimalbeispiel (JavaScript, VS Code)
function average(a, b, c){
// Setze Logpoint hier: 'avg-run a={a} b={b} c={c}'
return (a + b + c) / c; // Conditional Breakpoint: c === 0
}
console.log(average(5,10,15));
console.log(average(5,10,0)); // stoppt nur hier
4. Watch Expressions – Hypothesen prüfen
Wozu?
Du willst gezielt wissen, ob ein Ausdruck den erwarteten Wert hat – ohne zusätzliche print()s.
Was beobachten?
Einfache Variablen:
a,b,cAbgeleitete Ausdrücke:
(a + b + c),c === 0,(a + b + c) / c
Beispiel (Python, interaktives breakpoint())
def average(a, b, c):
breakpoint() # eingebauter Debugger-Stopp (Python 3.7+)
return (a + b + c) / c
print(average(5,10,0))
Im Debugger legst du Watchs an:
a, b, c(a+b+c),c == 0
5. Call-Stack – Ursache statt Symptom finden
Problem: Fehlermeldung in compute() – aber warum wurde compute() mit c=0 aufgerufen?
Lösung: Schau in den Call-Stack: Wer hat compute() gerufen, mit welchen Parametern?
Beispiel (Python):
def controller(vals):
return compute(vals[0], vals[1], vals[2])
def compute(a,b,c):
return (a+b+c) / c # Fehler tritt hier auf
print(controller([5,10,0]))
Im Stack siehst du: controller → compute.
Du gehst eine Ebene höher und prüfst dort, warum 0 reinkommt (Datenproblem? Validierung?).
6. Logging – klar, strukturiert, auswertbar
6.1 Log-Level & Struktur
Level: DEBUG (fein), INFO (Normal), WARN (auffällig), ERROR (Fehler), CRITICAL (Totalschaden).
Strukturiert loggen: immer kontext mitgeben (Funktion, Eingaben, Korrelation).
Python (sauberer Logger):
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
def average(a,b,c, req_id="—"):
logging.debug(f"[{req_id}] Eingabe a={a} b={b} c={c}")
if c == 0:
logging.error(f"[{req_id}] Ungültiger Nenner c=0")
raise ZeroDivisionError("Nenner c darf nicht 0 sein")
result = (a+b+c) / c
logging.info(f"[{req_id}] Ergebnis={result}")
return result
average(5,10,15, req_id="X123")
JavaScript (Browser/Node):
function average(a,b,c, ctx="—"){
console.debug(`[${ctx}] Eingabe`, {a,b,c});
if(c === 0){
console.error(`[${ctx}] Ungültiger Nenner c=0`);
throw new Error("Nenner c darf nicht 0 sein");
}
const result = (a+b+c)/c;
console.info(`[${ctx}] Ergebnis`, result);
return result;
}
average(5,10,15,"X123");
Best Practices (kompakt):
Immer Kontext loggen (IDs, Nutzer, Pfad).
Keine Geheimnisse (Passwörter, Tokens) loggen.
Fehler mit Stacktrace loggen (z. B.
logging.exception(...)in Python).Nicht spammen: DEBUG nur im Dev, INFO/WARN/ERROR in Prod.
7. Werkzeugwahl nach Fehlerbild
| Fehlerbild | Erstes Werkzeug | Ergänzung |
|---|---|---|
| Absturz/Exception (z. B. Division durch 0) | Debugger + Breakpoint vor der Zeile | Watch c == 0, Log mit Kontext |
| Falsches Ergebnis, kein Crash | Debugger (Step-Over) & Watch Ausdrücke | Zusätzliche Logs mit Zwischenwerten |
| Tritt nur manchmal auf | Conditional Breakpoint (z. B. c==0) | Log mit Eingabedaten / Korrelation |
| Unklare Herkunft („wo kommt der Call her?“) | Call-Stack inspizieren | Logging des Aufrufers, Request-ID |
| Server/Container, kein IDE-Zugriff | Logging (strukturiert, Level) | Remote Debugging/Attach, falls möglich |
