Java Fortgeschrittenen Schulung – Kursbezug
Ziel dieses Kapitels: Sie können Code lesbar strukturieren, technische Schulden reduzieren und pragmatische Standards im Team etablieren (Naming, Tests, Logging, Reviews, CI-Gates).
Worum geht’s?
- Lesbarkeit als Zeit- und Qualitätsfaktor: schneller verstehen → schneller ändern.
- Kleine, fokussierte Funktionen + klare Namen (Absicht statt Implementierungsdetails).
- Fehler sauber behandeln + sinnvolles Logging (Kontext, Level, keine stillen Fehler).
- Refactoring als Routine – abgesichert durch Tests.
- Team-Standards: Reviews, Formatierung, Checks im Build/CI.
Lehr-/Lernziele
- (LZ1) Code so strukturieren, dass er schnell verständlich ist (Naming, kleine Funktionen, klare Verantwortlichkeiten).
- (LZ2) typische „Code Smells“ erkennen und mit Refactoring beheben – abgesichert durch Tests.
- (LZ3) teamtaugliche Standards ableiten (Logging/Fehlerbehandlung, Reviews, Format/Lint, CI-Checks).
1) Naming & Absicht (Intent)
Vorher (unklar)
public double calc(double p, int t){
if(t == 1) return p * 0.9;
return p;
}Nachher (klar)
public double rabattAnwenden(double basisPreis, RabattTyp typ){
if(typ == RabattTyp.ONLINE_10_PROZENT) return basisPreis * 0.90;
return basisPreis;
}
public enum RabattTyp { KEINER, ONLINE_10_PROZENT }2) Kleine Funktionen & Single Responsibility
public class BuchungsService {
public BuchungsBestaetigung buchen(BuchungsAnfrage anfrage) {
validieren(anfrage);
double preis = preisBerechnen(anfrage);
speichern(anfrage, preis);
erfolgLoggen(anfrage, preis);
return new BuchungsBestaetigung(preis);
}
private void validieren(BuchungsAnfrage a) { /* ... */ }
private double preisBerechnen(BuchungsAnfrage a) { /* ... */ return 0; }
private void speichern(BuchungsAnfrage a, double preis) { /* ... */ }
private void erfolgLoggen(BuchungsAnfrage a, double preis) { /* ... */ }
}3) Fehler & Logging (pragmatisch)
- Exceptions: informativ (Kontext), nicht „schlucken“.
- Logging: Signal statt Rauschen (kein Log-Spam).
- Level:
INFOfür Ablauf/Business-Events,WARNINGfür ungewöhnlich,SEVEREfür echte Fehler.
import java.util.logging.*;
public class BuchungsService {
private static final Logger LOG = Logger.getLogger(BuchungsService.class.getName());
public void buchen(BuchungsAnfrage anfrage) {
try {
validieren(anfrage);
// ... Buchungslogik (Preis, Persistenz, etc.)
LOG.info(() -> "Buchung ok | kurs=" + anfrage.kursTitel() + " | kunde=" + anfrage.kundenId());
} catch (IllegalArgumentException ex) {
LOG.log(Level.WARNING,
"Buchung abgelehnt | grund=" + ex.getMessage()
+ " | kurs=" + safe(anfrage != null ? anfrage.kursTitel() : null)
+ " | kunde=" + safe(anfrage != null ? anfrage.kundenId() : null),
ex
);
throw ex;
} catch (RuntimeException ex) {
LOG.log(Level.SEVERE,
"Buchung fehlgeschlagen | kurs=" + safe(anfrage != null ? anfrage.kursTitel() : null)
+ " | kunde=" + safe(anfrage != null ? anfrage.kundenId() : null),
ex
);
throw ex;
}
}
private void validieren(BuchungsAnfrage a) {
if(a == null) throw new IllegalArgumentException("Anfrage fehlt");
if(a.kundenId() == null || a.kundenId().isBlank()) throw new IllegalArgumentException("Kunden-ID fehlt");
if(a.kursTitel() == null || a.kursTitel().isBlank()) throw new IllegalArgumentException("Kurs-Titel fehlt");
}
private static String safe(String s) { return (s == null || s.isBlank()) ? "(leer)" : s; }
}
record BuchungsAnfrage(String kundenId, String kursTitel) {}4) Refactoring mit Sicherheitsnetz (Tests)
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class PreisServiceTest {
@Test
void onlineRabattWirdAngewendet() {
PreisService s = new PreisService();
assertEquals(1874.25, s.endpreis(2082.50, RabattTyp.ONLINE_10_PROZENT), 0.001);
}
}
class PreisService {
double endpreis(double basisPreis, RabattTyp typ) {
if(basisPreis < 0) throw new IllegalArgumentException("Basispreis muss >= 0 sein");
return (typ == RabattTyp.ONLINE_10_PROZENT) ? basisPreis * 0.90 : basisPreis;
}
}
enum RabattTyp { KEINER, ONLINE_10_PROZENT }5) Team-Standards (CI/Review)
- Format: automatisch (z.B. Spotless) statt Diskussionen im Review.
- Static Analysis: Checkstyle/SpotBugs/Sonar (je nach Setup).
- Build-Gates: Tests müssen grün sein, bevor gemerged wird.
- Review-Checkliste: Naming, Komplexität, Tests, Logging, Security Basics.
Mini-Checkliste fürs Review
- Ist die Absicht der Methode/Klasse über Namen sofort klar?
- Gibt es „God Methods“ oder zu viele if/else-Zweige? (Komplexität)
- Gibt es Tests für kritische Pfade? (Happy Path + Fehlerfälle)
- Ist Logging sinnvoll (Kontext + Level), ohne sensible Daten?
- Sind Abhängigkeiten sauber gekapselt/injiziert? (weniger Kopplung)
Praxisaufgabe (Mini)
- Nehmen Sie eine Methode mit vielen Verantwortlichkeiten (z.B. „buchenUndBenachrichtigen“).
- Extrahieren Sie 3–4 private Methoden (validieren, preisBerechnen, speichern, loggen/notify).
- Benennen Sie Variablen/Methoden so, dass Kommentare überflüssig werden.
- Schreiben Sie 2 JUnit-Tests: Happy Path + Fehlerfall.
- Optional: Definieren Sie 5 Review-Regeln als Team-Standard.
Lösung (Refactoring-Skizze)
import java.util.logging.*;
public class BuchungsServiceRefactored {
private static final Logger LOG = Logger.getLogger(BuchungsServiceRefactored.class.getName());
private final PreisService preisService = new PreisService();
private final BuchungsRepository repo = new InMemoryBuchungsRepository();
public BuchungsBestaetigung buchen(BuchungsAnfrage anfrage) {
validieren(anfrage);
double preis = preisBerechnen(anfrage);
speichern(anfrage, preis);
LOG.info(() -> "Buchung ok | kurs=" + anfrage.kursTitel() + " | kunde=" + anfrage.kundenId() + " | preis=" + preis);
return new BuchungsBestaetigung(preis);
}
private void validieren(BuchungsAnfrage a) {
if(a == null) throw new IllegalArgumentException("Anfrage fehlt");
if(a.kundenId() == null || a.kundenId().isBlank()) throw new IllegalArgumentException("Kunden-ID fehlt");
if(a.kursTitel() == null || a.kursTitel().isBlank()) throw new IllegalArgumentException("Kurs-Titel fehlt");
}
private double preisBerechnen(BuchungsAnfrage a) {
// Beispiel: Online-Training bekommt 10% Rabatt
RabattTyp rabatt = (a.online() ? RabattTyp.ONLINE_10_PROZENT : RabattTyp.KEINER);
return preisService.endpreis(a.basisPreis(), rabatt);
}
private void speichern(BuchungsAnfrage a, double preis) {
repo.speichern(new Buchung(a.kundenId(), a.kursTitel(), preis));
}
}
/* ===== minimale Hilfsklassen für das Beispiel ===== */
record BuchungsAnfrage(String kundenId, String kursTitel, boolean online, double basisPreis) {}
record BuchungsBestaetigung(double preis) {}
record Buchung(String kundenId, String kursTitel, double preis) {}
interface BuchungsRepository { void speichern(Buchung b); }
class InMemoryBuchungsRepository implements BuchungsRepository {
public void speichern(Buchung b) { /* Demo: no-op */ }
}
enum RabattTyp { KEINER, ONLINE_10_PROZENT }
class PreisService {
double endpreis(double basisPreis, RabattTyp typ) {
if(basisPreis < 0) throw new IllegalArgumentException("Basispreis muss >= 0 sein");
return (typ == RabattTyp.ONLINE_10_PROZENT) ? basisPreis * 0.90 : basisPreis;
}
}Kurz-Takeaways
- Code wird für Menschen geschrieben: Namen + Struktur zuerst.
- Refactoring ist sicher, wenn Tests Verhalten absichern.
- Standards + CI sparen Review-Zeit und reduzieren Bugs.