Veröffentlicht am [Datum] | Von Elyndra Valen, Senior Entwicklerin bei Java Fleet Systems


Kurze Zusammenfassung – Das Wichtigste in 30 Sekunden

🎯 Worum geht’s: Die schockierende Realität meiner JDK-Migration: Eine 147-Zeilen-Monster-Methode mit Complexity 47, 23 kritische Security-Vulnerabilities und Dependencies aus 2011. Inklusive IntelliJ-Screenshots des Horror-Codes!

💡 Key Takeaways:

  • Legacy-Code aus 2014 nutzte damals sinnvolle Patterns (kein Urteilen!)
  • SQL Injection + Log4Shell + Klartext-Kreditkarten = Security-Nightmare
  • Monster-Methoden mit 11 Nesting-Level sind migration-ready nur mit Tests
  • Assessment VOR Refactoring VOR Migration ist überlebenswichtig

🎬 Was kommt: Nächste Woche die Lösung! 4-Phasen-Strategie, Characterization Tests und wie ich das Monster gezähmt habe.


Hi Java-Familie! 👋

Willkommen zu Teil 1 meiner JDK-Migration-Saga! Nach dem tollen Feedback auf meinen Intro-Post ist es Zeit für die ganze Wahrheit. Was ich euch heute zeige, ist nicht für schwache Nerven. Aber es ist real, es ist ehrlich, und es passiert täglich in Unternehmen weltweit.

Content Warning: Dieser Post enthält extremen Legacy-Code, Security-Vulnerabilities und Code-Complexity jenseits aller Vernunft. Viewer discretion advised! 🚨

🏛️ Der Auftrag: „Kann ja nicht so schwer sein…“

Montag, 8:00 Uhr – Franz-Martin, unser Teamleiter, ruft mich ins Meeting:

"Elyndra, wir haben da ein 'kleines' Modernisierung-Projekt. Payment-Service von JDK 8 auf 17. System läuft stabil seit 2014, sollte straightforward sein. Zeitschätzung: 4-6 Wochen."

Meine naive Reaktion: „JDK-Migration? Easy! Lambda-Syntax checken, Dependencies updaten, fertig!“

Famous last words. 🤦‍♀️

🔍 Tag 1: Der erste Blick – „Das sieht doch normal aus…“

Git-Repository geklont, IntelliJ gestartet, Projekt importiert. Erste Eindrücke:

payment-service/
├── src/main/java/com/company/payment/
│   ├── PaymentController.java
│   ├── PaymentService.java  
│   ├── PaymentProcessor.java    # 👀 Das wird interessant...
│   └── utils/
└── pom.xml                     # 👀 Oh no...

Mein Gedanke: „Sieht strukturiert aus. Was soll schon schief gehen?“

📊 Tag 2: Code-Analyse – Houston, we have a problem

Erste IntelliJ-Code-Inspektion gestartet. Die Ergebnisse haben mich vom Stuhl gehauen:

🚨 IntelliJ Inspection Results: Das Grauen in Zahlen

===============================
= CODE QUALITY INSPECTION     =
===============================

📁 Gesamtprojekt:
   - Lines of Code: 47,382
   - Files: 312
   - Critical Issues: 1,247
   - Major Issues: 3,891

🔴 TOP SEVERITY FINDINGS:
   - Cyclomatic Complexity > 30:     23 methods
   - Method Length > 100 lines:      89 methods  
   - Nesting Depth > 10:             12 methods
   - Dependency Vulnerabilities:     23 critical
   - JDK Internal API Usage:         156 usages

🏆 HALL OF SHAME CHAMPION:
   PaymentProcessor.processPayment()
   - Complexity: 47 😱
   - Lines: 147 
   - Nesting: 11 levels
   - Parameters: 10
   - Test Coverage: 0%

Screenshot-Moment: [Hier würde das IntelliJ-Screenshot mit dem Legacy-Code stehen]

Meine Reaktion: „Das… das kann nicht echt sein. Niemand schreibt SO CODE!“

Spoiler: Es ist echt. Und es läuft produktiv. Seit 10 Jahren. 😰

🐉 Das Monster: PaymentProcessor.processPayment()

Bereitet euch vor. Das hier ist DER BOSS-FIGHT meiner Code-Archäologie-Karriere:

Die Monster-Methode in ihrer vollen… Pracht

/**
 * PaymentProcessor - Legacy Code aus 2014
 * WARNUNG: Nesting Level 11, Complexity 47!
 * Letzter Edit: "FIX ASAP - PROD ISSUE" (2019)
 */
public String processPayment(HttpServletRequest request, String paymentType, 
                           BigDecimal amount, String currency, String customerId,
                           String cardNumber, String expiryDate, String cvv,
                           String billingAddress, Map<String, Object> metadata) {
    
    String result = "";
    PreparedStatement stmt = null;
    ResultSet rs = null;
    
    try {
        // Nesting Level 1
        if (request != null) {
            String userAgent = request.getHeader("User-Agent");
            
            // Nesting Level 2  
            if (userAgent != null && !userAgent.isEmpty()) {
                
                // Nesting Level 3
                if (paymentType != null) {
                    
                    // Nesting Level 4
                    if (paymentType.equals("CREDIT_CARD")) {
                        
                        // Nesting Level 5
                        if (amount != null && amount.compareTo(BigDecimal.ZERO) > 0) {
                            
                            // Nesting Level 6
                            if (currency != null && 
                               (currency.equals("EUR") || currency.equals("USD") || currency.equals("GBP"))) {
                                
                                // 🚨 SQL INJECTION VULNERABILITY! 🚨
                                String sql = "SELECT * FROM customers WHERE id = '" + customerId + "'";
                                stmt = dbConnection.prepareStatement(sql);
                                rs = stmt.executeQuery();
                                
                                // Nesting Level 7
                                if (rs.next()) {
                                    String customerStatus = rs.getString("status");
                                    String customerType = rs.getString("type");
                                    
                                    // Nesting Level 8
                                    if (customerStatus.equals("ACTIVE")) {
                                        
                                        // Nesting Level 9
                                        if (cardNumber != null && cardNumber.length() >= 13) {
                                            
                                            // Primitive Kreditkarten-Validierung 🤦‍♀️
                                            boolean isValidCard = false;
                                            if (cardNumber.startsWith("4")) { // Visa
                                                isValidCard = true;
                                            } else if (cardNumber.startsWith("5")) { // MasterCard
                                                isValidCard = true;
                                            } else if (cardNumber.startsWith("3")) { // Amex
                                                isValidCard = true;
                                            }
                                            
                                            // Nesting Level 10
                                            if (isValidCard) {
                                                
                                                // Date-Parsing OHNE Exception Handling! 
                                                SimpleDateFormat sdf = new SimpleDateFormat("MM/yy");
                                                Date expiry = sdf.parse(expiryDate);
                                                Date now = new Date();
                                                
                                                // NESTING LEVEL 11 - MAXIMUM ERREICHT! 🔥
                                                if (expiry.after(now)) {
                                                    
                                                    // Hardcoded Business Logic everywhere!
                                                    BigDecimal fee = BigDecimal.ZERO;
                                                    if (customerType.equals("PREMIUM")) {
                                                        fee = amount.multiply(new BigDecimal("0.01")); // 1%
                                                    } else if (customerType.equals("STANDARD")) {
                                                        fee = amount.multiply(new BigDecimal("0.025")); // 2.5%
                                                    } else {
                                                        fee = amount.multiply(new BigDecimal("0.05")); // 5%
                                                    }
                                                    
                                                    // Weitere Business Logic...
                                                    result = processPaymentInternal(customerId, cardNumber, 
                                                                                   amount.add(fee), currency);
                                                    
                                                    // 🚨 KREDITKARTE IM KLARTEXT GELOGGT! 🚨
                                                    System.out.println("Payment processed for card: " + cardNumber);
                                                    
                                                } else {
                                                    result = "ERROR: Card expired";
                                                }
                                            } else {
                                                result = "ERROR: Invalid card number";
                                            }
                                        } else {
                                            result = "ERROR: Invalid card number length";
                                        }
                                    } else {
                                        result = "ERROR: Customer not active";
                                    }
                                } else {
                                    result = "ERROR: Customer not found";
                                }
                            } else {
                                result = "ERROR: Invalid currency";
                            }
                        } else {
                            result = "ERROR: Invalid amount";
                        }
                    } else {
                        result = "ERROR: Unknown payment type";
                    }
                } else {
                    result = "ERROR: Payment type is null";
                }
            } else {
                result = "ERROR: Invalid user agent";
            }
        } else {
            result = "ERROR: Request is null";
        }
        
    } catch (Exception e) {
        result = "ERROR: " + e.getMessage();
        // TODO: Proper error handling (Kommentar aus 2014!)
    } finally {
        // Resource cleanup OHNE try-with-resources
        try {
            if (rs != null) rs.close();
            if (stmt != null) stmt.close();
        } catch (SQLException e) {
            // Ignoriere cleanup errors... 🤷‍♀️
        }
    }
    
    return result;
}

Meine Reaktion nach dem ersten Durchlesen:

„WHAT. THE. ACTUAL. F… Das ist ein BOSS-FIGHT!“ 😱

Die Horror-Statistics der Monster-Methode:

  • 147 Zeilen Code in einer Methode
  • Cyclomatic Complexity: 47 (SonarQube Limit: 10!)
  • 11 Nesting-Level (if inside if inside if…)
  • 10 Parameter (Clean Code Limit: 3)
  • Zero Tests (natürlich!)
  • Hardcoded Business Logic überall
  • SQL Injection Vulnerability
  • Kreditkarten im Klartext geloggt

🔒 Security-Horror: Die 23 kritischen Vulnerabilities

Nach dem Code-Schock kam der Security-Schock. OWASP Dependency Check Ergebnisse:

🚨 Critical Security Issues (Auszug):

===============================
= OWASP DEPENDENCY CHECK      =
===============================

🔴 CRITICAL (Score 9.0+):
   - CVE-2021-44228: Log4j RCE        # Log4Shell!
   - CVE-2021-45046: Log4j DoS
   - CVE-2019-12384: Jackson XXE
   - CVE-2020-8840: Jackson RCE
   - CVE-2018-11771: Commons-Compress

🟠 HIGH (Score 7.0-8.9):
   - 18 weitere High-Severity Issues

💣 TOTAL IMPACT:
   - Remote Code Execution: Möglich
   - Data Exfiltration: Möglich  
   - System Compromise: Wahrscheinlich
   - Compliance Violation: Garantiert

Fun Fact: Das System läuft mit Log4j 1.2.17 – praktisch jede bekannte Java-Security-Lücke der letzten 5 Jahre ist vorhanden!

📦 Dependency-Archäologie: Das Museum der vergessenen Libraries

Dann die pom.xml – ein wahres IT-Museum:

<!-- The Hall of Fame der veralteten Dependencies -->
<properties>
    <java.version>1.8</java.version>
    <spring.version>3.2.18.RELEASE</spring.version>  <!-- 2014! -->
    <hibernate.version>4.3.11.Final</hibernate.version>  <!-- 2015! -->
</properties>

<dependencies>
    <!-- Servlet API aus der Steinzeit -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version> <!-- Released: 2005! -->
    </dependency>
    
    <!-- Commons Lang 2.x - Pre-Historic -->
    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.6</version> <!-- Last update: 2011! -->
    </dependency>
    
    <!-- Jackson aus dem Mesozoikum -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.9.10</version> <!-- 2019, aber völlig veraltet -->
    </dependency>
    
    <!-- JUnit 4 - Nostalgic Testing -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    
    <!-- Log4j 1.x - The Security Nightmare -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version> <!-- CRITICAL VULNERABILITIES! -->
    </dependency>
</dependencies>

Historical Context: Diese Dependencies sind so alt, dass:

  • Commons Lang 2.6: Obama war gerade Präsident geworden
  • Spring 3.2: Gleichzeitig mit Windows 8 released
  • Servlet 2.5: Aus der Zeit, als YouTube gestartet wurde
  • Log4j 1.2.17: Älter als das erste iPhone

🎭 Code-Archäologie-Moment: Zeitzeugnis verstehen

Wichtig: Ich urteile nicht über die damaligen Entwickler! Diese Patterns machten 2014 absolut Sinn:

2014 Java-Reality Check:

Modern Developer: „Warum kein Optional für Null-Safety?“
2014 Reality: „Optional wurde gerade erst mit Java 8 eingeführt.“

Modern Developer: „Warum keine Stream API für Collections?“
2014 Reality: „Streams waren brandneu und ‚experimentell‘.“

Modern Developer: „Warum keine Microservices?“
2014 Reality: „Netflix hatte gerade mal ihre ersten Microservices-Talks gegeben.“

Franz-Martin’s Zeitzeugnis: „Lass mich dir erklären…“

Neugierig geworden bin ich zu Franz-Martin gegangen – unser Teamleiter war schließlich 2014 schon Senior Developer. Das Gespräch war… augenöffnend.


Elyndra: „Franz-Martin, ich muss dich was fragen. Dieser PaymentProcessor-Code… warum wurde der so geschrieben? Das sind 11 Nesting-Level!“

Franz-Martin: (lacht) „Ah, du hast die Payment-Logik gefunden! Weißt du, Elyndra, das war 2014 State-of-the-Art. Lass mich dir den Kontext erklären…“

Franz-Martin: „Java 8 war gerade rausgekommen – Lambda-Expressions galten als ‚experimentell‘. Die meisten Unternehmen liefen noch auf Java 7! Optional? Das nutzte niemand produktiv.“

Elyndra: „Aber die Nesting-Level… das ist doch schon damals schlecht gewesen?“

Franz-Martin: „Nicht schlecht – pragmatisch! Wir hatten 2 Wochen für das Feature, keine Unit-Testing-Kultur, und ‚Clean Kode‘ war noch nicht mainstream. Die Geschäftslogik musste funktionieren – und sie tat es 10 Jahre lang!“


Mein AHA-Moment: Franz-Martin hatte recht. 2014 war eine andere Zeit:

  • TDD war Theorie, nicht Praxis
  • Clean Code war ein Buch, kein Standard
  • Continuous Integration war noch selten
  • Code Reviews waren optional
  • Legacy zu schreiben war… normal

Franz-Martin’s Gegenfrage: Der Generationen-Twist

Franz-Martin: „Aber erkläre mir mal, Elyndra – warum nutzt ihr heute Records und Pattern Matching für alles? Ist das nicht auch nur ein Hype?“

Elyndra: „Äh… nein! Es ist… moderner?“

Franz-Martin: (grinst) „Das sagten wir 2014 über Anonymous Inner Classes auch. Was denkst du – wird dein heutiger Code in 10 Jahren auch ‚Legacy-Horror‘ sein?“

Elyndra: „…“ (stille)

Franz-Martin: „Code-Archäologie funktioniert in beide Richtungen, junge Padawan. Respektiere die Vergangenheit, lerne für die Zukunft.“


Migration ist Zeitreise-Übersetzung

Nach diesem Gespräch verstand ich meine Aufgabe als Code-Archäologin:

  1. Verstehen warum der Code 2014 Sinn gemacht hat
  2. Respektieren die Entscheidungen der damaligen Entwickler
  3. Übersetzen in moderne Äquivalente
  4. Bewahren die Business-Logic und Intention

Nicht urteilen, sondern modernisieren.

Und Franz-Martin? Der lernte auch was: „Diese Characterization-Tests-Idee ist brilliant, Elyndra. Das hätten wir 2014 gebraucht!“

😰 Freitag 17:00 Uhr: Der Cliffhänger

Nach einer Woche Code-Archäologie sitze ich hier, starre auf meinen Bildschirm und denke:

„Wie zur Hölle soll ich DAS migrieren?“

Die Migration von JDK 8 auf JDK 17 ist eine Sache. Aber dieser Code… das ist nicht nur Migration, das ist digitale Notfall-Chirurgie!

Die Herausforderungen sind massiv:

JDK 8 → 17: 9 Jahre Technologie-Evolution
Security-Löcher: 23 kritische Vulnerabilities
Zero Test Coverage: Auf critical Paths
Monster-Methoden: Complexity 47+
Legacy Dependencies: Zum Teil 15+ Jahre alt
Production System: Kann nicht einfach offline genommen werden

Ah Oracle hat ein Migrations Guide?

Die Fragen, die mich umtreiben:

🤔 Kann ich das überhaupt migrieren ohne alles kaputt zu machen?
🤔 Wo fange ich an bei diesem Chaos?
🤔 Wie bringe ich Tests in ein System ohne Tests?
🤔 Wie überzeuge ich das Management von 6+ Monaten Modernisierung?

🎬 TO BE CONTINUED…

Das hier ist zu groß für einen Blog-Post.

Nächste Woche erzähle ich euch:

  • 🔧 Meine Survival-Strategie für diese Migration
  • 📋 Der 4-Phasen-Schlachtplan (Assessment → Preparation → Migration → Modernization)
  • 🧪 Wie ich Tests in untestbaren Code bringe
  • ⚔️ Der Kampf gegen die PaymentProcessor-Bestie
  • 🏆 Erste Erfolge (Spoiler: Es gibt Hoffnung!)

Bis dahin: Community-Horror-Stories gesucht! 👻

📧 Schreibt mir: elyndra.valen@java-developer.online

Ich will eure JDK-Migration-Alpträume hören:

  • Welche Monster-Methoden habt ihr gefunden?
  • Dependency-Hell-Geschichten aus euren Projekten?
  • Wie lange hat eure letzte JDK-Migration gedauert?
  • Security-Vulnerabilities, die euch den Schlaf geraubt haben?

Die besten Horror-Stories nehme ich in den nächsten Post auf! 🎃

Ein Lichtblick zum Wochenende:

Falls ihr nach diesem Post Alpträume habt – keine Sorge! Als Code-Archäologin habe ich schon schlimmere Ruinen restauriert.

JDK-Migration ist schwer, aber nicht unmöglich. Mit der richtigen Strategie, Tests und einer Menge Kaffee ☕ kriegen wir das hin!

Stay tuned für den Epic-Conclusion nächste Woche! 🚀


FAQ – Erste Hilfe bei JDK-Migration-Schock

Frage 1: Ist es normal, dass Legacy-Code so erschreckend aussieht?
Antwort: Leider ja! Systeme, die 2014 gebaut wurden, nutzen damals aktuelle Standards. Anonymous Inner Classes, Date/Calendar-API und null-checks waren völlig normal. Es ist nicht „schlechter“ Code – es ist ein Zeitzeugnis der Java-Evolution.

Frage 2: Sollte ich bei so viel Legacy-Code nicht lieber alles neu schreiben?
Antwort: NEIN! „Rewrite from scratch“ ist fast immer ein Fehler. Der Legacy-Code hat Jahre der Bug-Fixes und Business-Logic-Anpassungen. Schrittweise Migration bewahrt dieses Wissen und ist viel sicherer.

Frage 3: Wie erkenne ich Security-Vulnerabilities in Legacy-Code?
Antwort: SonarQube-Scan (wie ich gezeigt habe) plus OWASP Dependency Check. Die kritischsten: SQL-Injection, XXE, veraltete Dependencies. Priorisiert Security-Fixes VOR Feature-Entwicklung!

Frage 4: Was mache ich mit Methoden, die Complexity >30 haben?
Antwort: Erstmal Characterization Tests schreiben (dokumentiert aktuelles Verhalten), dann schrittweise Extract Method Refactoring. Nie alles auf einmal! Eine Nesting-Ebene nach der anderen eliminieren.

Frage 5: Wie überzeuge ich Management von 6+ Monaten Migration-Aufwand?
Antwort: Security-Risiken, Compliance-Probleme und Recruiting-Schwierigkeiten betonen. „Niemand will auf JDK 8 entwickeln“ ist ein starkes Argument. Plus: Performance-Gains und niedrigere Wartungskosten nach Migration.


🔮 Was als nächstes kommt

📅 Nächste Woche – Teil 2: „Survival-Strategie & Der Kampf beginnt“

  • Meine 4-Phasen-Strategie gegen das Monster
  • Characterization Tests: Wie ich das Verhalten dokumentiert habe
  • Extract Method Refactoring: Von 147 Zeilen zu 12 kleinen Methoden
  • Security-Fixes: SQL Injection eliminiert, Log4Shell gepatcht
  • JDK 8→11→17: Der Migrations-Marathon beginnt

📅 In 2 Wochen – Teil 3: „Victory & Community Success Stories“

  • Zero-Downtime Production-Rollout
  • Performance-Gains: 54% Throughput-Verbesserung gemessen!
  • Community Hall of Fame mit euren Migration-Triumphen
  • Komplettes Migration-Playbook als kostenloser Download

🎁 Bonus: Alle Leser bekommen meine Characterization-Tests-Templates und Refactoring-Checklisten!


Folgt Elyndras Code-Archäologie-Saga! [Blog abonnieren] – nächste Woche wird alles besser! 🚀


Avatar-Foto

Elyndra Valen

28 Jahre alt, wurde kürzlich zur Senior Entwicklerin befördert nach 4 Jahren intensiver Java-Entwicklung. Elyndra kennt die wichtigsten Frameworks und Patterns, beginnt aber gerade erst, die tieferen Zusammenhänge und Architektur-Entscheidungen zu verstehen. Sie ist die Brücke zwischen Junior- und Senior-Welt im Team.

0 Kommentare

Schreibe einen Kommentar

Avatar-Platzhalter

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert