IT-Übungen

Debugging-Denken

Fehlerszenarien, Logs, Stacktraces, Flaky Tests, Nebenläufigkeit.

Lesestand

  1. Nur montags rot ★★☆ Flaky Tests · Datum

    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.

  2. Einzeln grün, in der Suite rot ★★☆ Flaky Tests · Testisolation

    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.

  3. Thread.sleep im Test ★★☆ Flaky Tests · Asynchronität
    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.

  4. Kaputte Umlaute ★★☆ Zeichensätze · Encoding

    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.

  5. Stacktrace lesen ★★☆ Stacktraces · Exceptions
    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 more

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

  6. git bisect ★★☆ Versionskontrolle · Binärsuche

    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.

  7. Langsamen Schritt finden ★☆☆ Logs · Performance
    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.

  8. Zahlenformat auf dem Server ★★★ Locale · Internationalisierung

    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.

  9. Strg + F5 hilft ★☆☆ Caching · Web

    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.

  10. Speicher wächst stetig ★★★ Speicher · Performance

    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.

  11. Doppelt gebucht ★★★ Nebenläufigkeit · Race Condition
    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.

  12. Eine Stunde daneben ★★☆ Datum · Zeitzonen

    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.

  13. Frau Null ★★★ null · Datenqualität

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

  14. Der erste Schritt ★☆☆ Methodik

    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.

  15. Der Fehler verschwindet im Debugger ★★★ Nebenläufigkeit · Heisenbug

    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.

  16. Logs: die Grundursache ★★☆ Logs · Ursachenanalyse
    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.

  17. Assertion mit Kommazahl ★☆☆ Testen · Gleitkomma
    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.

  18. Langsam nur in Produktion ★★★ Performance · Datenbank

    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.

  19. 401 oder 403 ★☆☆ HTTP · Sicherheit

    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.

  20. Seit gestern kaputt ★★☆ Methodik · Änderungsmanagement

    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.