Debugging-Denken
Fehlerszenarien, Logs, Stacktraces, Flaky Tests, Nebenläufigkeit.
- Nur montags rot ★★☆ Flaky Tests · Datum ✓ gelöst
Ein Test läuft lokal immer grün. Im nächtlichen CI-Lauf schlägt er nur in der Nacht auf Montag fehl, an allen anderen Tagen ist er grün.
Was ist die wahrscheinlichste Ursache?
Lösung anzeigen
Richtig: Datums- oder Wochentagslogik im Code oder in den Testdaten
Ein Muster, das an einen Wochentag gebunden ist, deutet fast immer auf Datumslogik hin: „gestern“ ist montags ein Sonntag, „letzter Werktag“ liegt drei Tage zurück, Wochenwechsel. Abhilfe: im Test eine feste Uhr verwenden (java.time.Clock) statt der echten Systemzeit.
- Einzeln grün, in der Suite rot ★★☆ Flaky Tests · Testisolation ✓ gelöst
Ein Test schlägt fehl, wenn die ganze Testsuite läuft. Startet man ihn einzeln, ist er grün.
Was ist die wahrscheinlichste Ursache?
Lösung anzeigen
Richtig: Ein anderer Test hinterlässt Zustand, z. B. Daten in der Datenbank oder eine statische Variable
Tests müssen unabhängig voneinander sein. Wenn die Reihenfolge eine Rolle spielt, verändert ein vorheriger Test etwas Gemeinsames: Datenbankeinträge, Caches, statische Felder, Systemeigenschaften. Eingrenzen: Tests in anderer Reihenfolge laufen lassen und schrittweise halbieren, bis der störende Test gefunden ist.
- Thread.sleep im Test ★★☆ Flaky Tests · Asynchronität ✓ gelöst
Testcode: service.starteImport(); Thread.sleep(500); assertEquals(100, repository.anzahl()); Meist grün, gelegentlich rot mit „expected 100 but was 73“.
Wie behebt man das am besten?
Lösung anzeigen
Richtig: Statt fester Wartezeit auf die Bedingung warten, mit Zeitlimit (z. B. Awaitility)
Eine feste Wartezeit ist entweder zu kurz (flaky) oder zu lang (langsam), auf belasteten CI-Servern oft beides. Besser ist, regelmäßig zu prüfen, ob die Bedingung erfüllt ist, und nach einem großzügigen Zeitlimit aufzugeben. Automatische Wiederholungen verstecken das Problem nur.
- Kaputte Umlaute ★★☆ Zeichensätze · Encoding ✓ gelöst
In der Kundenliste steht „Müller“ statt „Müller“.
Was ist passiert?
Lösung anzeigen
Richtig: UTF-8-Text wurde als ISO-8859-1 (Latin-1) gelesen
In UTF-8 wird „ü“ mit zwei Bytes gespeichert (C3 BC). Liest man sie als Latin-1, wird jedes Byte zu einem eigenen Zeichen: „Ó und „¼“. Das typische Muster „Ó plus ein weiteres Zeichen verrät diesen Fall sofort. Der umgekehrte Fall erzeugt meist das Ersatzzeichen „�“. Lösung: Zeichensatz überall ausdrücklich auf UTF-8 setzen.
- Stacktrace lesen ★★☆ Stacktraces · Exceptions ✓ gelöst
Exception in thread "main" java.lang.RuntimeException: Bestellung fehlgeschlagen at de.shop.BestellService.abschliessen(BestellService.java:88) at de.shop.Main.main(Main.java:12) Caused by: java.lang.NullPointerException: Cannot invoke "String.length()" because "plz" is null at de.shop.AdressValidator.pruefe(AdressValidator.java:41) at de.shop.BestellService.abschliessen(BestellService.java:80) ... 1 moreWo sollte man zuerst nachsehen?
Lösung anzeigen
Richtig: AdressValidator.java:41, dort ist plz null
Die eigentliche Ursache steht im letzten „Caused by“, und dort in der obersten Zeile. Die RuntimeException darüber ist nur eine Hülle. Die Meldung nennt sogar die Variable: plz ist null. Danach fragt man, warum plz null ist, also wo die Adresse herkommt.
- git bisect ★★☆ Versionskontrolle · Binärsuche ✓ gelöst
Zwischen der letzten fehlerfreien Version und der aktuellen liegen 1000 Commits. Mit git bisect testet man jeweils den Commit in der Mitte und halbiert so den Suchbereich.
Wie viele Tests braucht man höchstens, um den fehlerhaften Commit zu finden?
Lösung anzeigen
Richtig: 10
Jeder Test halbiert den Bereich: 1000, 500, 250 … nach 10 Schritten bleibt ein Commit (2¹⁰ = 1024). Lässt sich der Test automatisieren, erledigt „git bisect run“ das sogar ohne Zutun.
- Langsamen Schritt finden ★☆☆ Logs · Performance ✓ gelöst
Log einer Bestellung, die 3,9 Sekunden dauert: 10:15:02.120 Anfrage empfangen: POST /bestellung 10:15:02.135 Benutzer geladen 10:15:02.180 Warenkorb geladen 10:15:05.940 Lagerbestand geprüft 10:15:06.010 Zahlung autorisiert 10:15:06.020 Antwort gesendet
Welcher Schritt verursacht die Verzögerung?
Lösung anzeigen
Richtig: Die Lagerbestandsprüfung (3,76 s)
Jede Logzeile markiert das Ende eines Schritts. Entscheidend ist die Differenz zur vorherigen Zeile: Zwischen „Warenkorb geladen“ und „Lagerbestand geprüft“ liegen 3,76 Sekunden, alle anderen Schritte dauern Millisekunden. Ein typischer Kandidat wäre hier ein langsamer externer Dienst.
- Zahlenformat auf dem Server ★★★ Locale · Internationalisierung ✓ gelöst
Ein Preis von „1.234,56“ wird korrekt verarbeitet, solange die Anwendung auf deutschen Servern läuft. Nach dem Umzug in ein US-Rechenzentrum wird daraus 1,23456.
Was ist die wahrscheinlichste Ursache?
Lösung anzeigen
Richtig: Das Zahlenformat hängt von der Standard-Ländereinstellung (Locale) des Servers ab
In den USA ist der Punkt das Dezimaltrennzeichen und das Komma das Tausendertrennzeichen, genau umgekehrt zu Deutschland. Ohne ausdrücklich gesetztes Locale (z. B. NumberFormat.getInstance(Locale.GERMANY)) nimmt Java das des Servers. Testidee: Testsysteme bewusst mit fremdem Locale betreiben.
- Strg + F5 hilft ★☆☆ Caching · Web ✓ gelöst
Nach einem Deployment melden einige Nutzer, dass ein neuer Button fehlt und die Seite Fehler anzeigt. Wer Strg + F5 drückt, sieht alles korrekt.
Was ist die Ursache?
Lösung anzeigen
Richtig: Der Browser verwendet noch alte, zwischengespeicherte JavaScript- oder CSS-Dateien
Strg + F5 lädt alles neu am Cache vorbei. Wenn das hilft, war der Cache das Problem. Die übliche Lösung ist, Dateinamen beim Build mit einem Versions- oder Hash-Anteil zu versehen (app.3f9a2c.js), sodass der Browser neue Dateien zwingend neu lädt.
- Speicher wächst stetig ★★★ Speicher · Performance ✓ gelöst
Der Speicherverbrauch eines Dienstes steigt nach jedem Neustart gleichmäßig an. Nach etwa drei Tagen stürzt er mit OutOfMemoryError ab. Die Last ist über die Tage gleich.
Was ist die wahrscheinlichste Ursache?
Lösung anzeigen
Richtig: Ein Speicherleck, z. B. ein Cache oder eine statische Map, die nie geleert wird
Gleichmäßiges Wachstum bei gleicher Last heißt: Etwas wird festgehalten, das nie mehr freigegeben wird. Mehr Speicher verschiebt den Absturz nur. Ein Heap-Dump (z. B. mit Eclipse MAT ausgewertet) zeigt, welche Objekte sich ansammeln.
- Doppelt gebucht ★★★ Nebenläufigkeit · Race Condition ✓ gelöst
Code einer Buchung: if (platz.istFrei()) { platz.reservieren(kunde); } Zwei Kunden klicken im selben Moment auf den letzten freien Platz. Beide erhalten eine Bestätigung.Was ist die Ursache?
Lösung anzeigen
Richtig: Eine Race Condition: Beide prüfen „frei“, bevor einer von beiden reserviert
Prüfen und Reservieren sind zwei getrennte Schritte. Dazwischen kann ein anderer Thread dieselbe Prüfung machen. Lösung: beides atomar machen, z. B. per Datenbanksperre, eindeutiger Einschränkung (unique constraint) oder bedingtem Update („UPDATE … WHERE frei = true“ und betroffene Zeilen zählen). Testen lässt sich das nur mit echten parallelen Anfragen.
- Eine Stunde daneben ★★☆ Datum · Zeitzonen ✓ gelöst
Seit dem letzten Sonntag im Oktober zeigen gespeicherte Termine eine Stunde zu früh an. Vorher war alles korrekt.
Was ist die wahrscheinlichste Ursache?
Lösung anzeigen
Richtig: Die Umstellung von Sommer- auf Winterzeit wird nicht berücksichtigt
Ein Fehler, der genau an einem Umstellungstag beginnt, verrät die Ursache. Typisch: Zeiten werden mit festem Versatz (+01:00 oder +02:00) statt mit Zeitzone (Europe/Berlin) gespeichert. Mit ZonedDateTime und einer echten Zeitzone rechnet Java die Umstellung automatisch richtig.
- Frau Null ★★★ null · Datenqualität ✓ gelöst
Eine Kundin mit dem Nachnamen „Null“ kann sich nicht registrieren. Alle anderen Namen funktionieren.
Was ist die wahrscheinlichste Ursache?
Lösung anzeigen
Richtig: Irgendwo wird der Text "null" als fehlender Wert interpretiert
Das passiert wirklich, z. B. wenn Werte als Text übertragen und mit String.valueOf(null) erzeugt oder mit "null".equals(...) geprüft werden. Ähnliche Fälle: Nachnamen wie „True“ oder Orte wie „NaN“. Für Testdaten lohnt eine Liste solcher Problemwerte (Big List of Naughty Strings).
- Der erste Schritt ★☆☆ Methodik ✓ gelöst
Ein Kunde meldet: „Manchmal wird mein Rabatt falsch berechnet.“ Mehr ist nicht bekannt.
Was ist der sinnvollste erste Schritt?
Lösung anzeigen
Richtig: Den Fehler zuverlässig reproduzieren und die genauen Bedingungen ermitteln
Ohne Reproduktion weiß man weder, wo man suchen muss, noch, ob ein Fix wirkt. Also: Welche Bestellung, welcher Rabatt, welche Artikel? Ziel ist ein möglichst kleines Beispiel, das den Fehler sicher zeigt. Daraus wird später gleich ein Regressionstest.
- Der Fehler verschwindet im Debugger ★★★ Nebenläufigkeit · Heisenbug ✓ gelöst
Ein Fehler tritt im normalen Betrieb regelmäßig auf. Sobald man einen Breakpoint setzt und Schritt für Schritt durchgeht, läuft alles korrekt.
Worauf deutet das hin?
Lösung anzeigen
Richtig: Ein Timing-Problem, z. B. Nebenläufigkeit oder ein Wettlauf zwischen Threads
Ein Breakpoint verändert das Timing: Andere Threads bekommen plötzlich genug Zeit. Solche Fehler heißen nach der Unschärferelation „Heisenbugs“. Besser beobachtet man hier mit Logging (mit Thread-Namen und genauen Zeitstempeln) statt mit Anhalten.
- Logs: die Grundursache ★★☆ Logs · Ursachenanalyse ✓ gelöst
08:00:01 INFO Anwendung gestartet 08:12:44 WARN Datenbank-Verbindungspool zu 90 % ausgelastet 08:13:02 ERROR Keine freie Datenbankverbindung (Pool erschöpft) 08:13:05 ERROR Timeout bei GET /api/kunden 08:13:05 ERROR Timeout bei GET /api/bestellungen 08:13:07 ERROR Timeout bei GET /api/kunden
Was ist die wahrscheinlichste Grundursache?
Lösung anzeigen
Richtig: Der Datenbank-Verbindungspool ist erschöpft, z. B. weil Verbindungen nicht freigegeben werden
Die vielen Timeouts sind Folgefehler. Entscheidend ist die erste Auffälligkeit: Warnung zum Pool, dann der erste ERROR. Faustregel: In einer Fehlerlawine von oben nach unten lesen und beim ersten Fehler anfangen, nicht beim häufigsten.
- Assertion mit Kommazahl ★☆☆ Testen · Gleitkomma ✓ gelöst
assertEquals(0.3, rechner.summe(0.1, 0.2)); Fehlermeldung: expected <0.3> but was <0.30000000000000004>
Wie korrigiert man den Test?
Lösung anzeigen
Richtig: Mit Toleranz vergleichen: assertEquals(0.3, ergebnis, 0.0001)
Gleitkommazahlen sind nicht exakt. JUnit bietet dafür eine Variante mit erlaubter Abweichung (delta). Den krummen Wert einzutragen macht den Test vom Rechenweg abhängig. Geht es um Geld, ist BigDecimal ohnehin die bessere Wahl.
- Langsam nur in Produktion ★★★ Performance · Datenbank ✓ gelöst
Eine Kundenübersicht lädt im Testsystem in 0,2 Sekunden, in Produktion in 30 Sekunden. Das SQL-Log zeigt pro Seitenaufruf über 5000 Abfragen der Form: SELECT * FROM adresse WHERE kunde_id = ?
Was ist die Ursache?
Lösung anzeigen
Richtig: Das N+1-Problem: Für jeden Kunden wird die Adresse einzeln nachgeladen
Eine Abfrage für die Liste, dann eine pro Kunde: bei 20 Testkunden unauffällig, bei 5000 echten Kunden fatal. Lösung: Adressen in einer Abfrage mitladen (JOIN oder JOIN FETCH). Lehre für das Testen: Performance immer auch mit realistischer Datenmenge prüfen.
- 401 oder 403 ★☆☆ HTTP · Sicherheit ✓ gelöst
Eine angemeldete Nutzerin ruft eine Verwaltungsseite auf und erhält den HTTP-Status 403.
Was bedeutet das?
Lösung anzeigen
Richtig: Sie ist angemeldet, hat aber keine Berechtigung für diese Seite
401 (Unauthorized) heißt trotz des Namens: nicht angemeldet bzw. Anmeldung ungültig. 403 (Forbidden): bekannt, aber nicht berechtigt. 404: nicht gefunden, 503: Dienst nicht verfügbar. Für Tests ist der Unterschied wichtig, weil beide Fälle unterschiedlich behandelt werden müssen.
- Seit gestern kaputt ★★☆ Methodik · Änderungsmanagement ✓ gelöst
Eine Funktion, die seit Monaten stabil läuft, funktioniert seit gestern nicht mehr. Am Code der Funktion hat niemand etwas geändert.
Wo sucht man am sinnvollsten zuerst?
Lösung anzeigen
Richtig: Bei allem, was sich seit gestern geändert hat: Deployments, Konfiguration, Bibliotheken, Daten, externe Dienste, Zertifikate
Wenn etwas plötzlich nicht mehr geht, hat sich etwas geändert, nur eben nicht unbedingt der Code. Häufige Kandidaten: abgelaufene Zertifikate oder Passwörter, eine geänderte Schnittstelle eines Partners, neue Daten mit ungewöhnlichen Werten, ein automatisches Bibliotheks-Update.