Entwurf und Architektur

In dieser Lerneinheit tauchst du in die Prinzipien des qualitätsgetriebenen Softwareentwurfs (Quality-Driven Design) ein. Du lernst, wie du Softwarearchitektur und Systemdesign von Anfang an auf wichtige Qualitätsmerkmale wie Wartbarkeit, Performance und Sicherheit ausrichtest. Diese Kenntnisse helfen dir dabei, robuste und zukunftssichere Softwarelösungen zu entwickeln, die sich später leichter anpassen und erweitern lassen.

Einführung

Stell dir vor: Du bist Teil eines Entwicklerteams, das eine E-Commerce-Plattform baut. Nach sechs Monaten intensiver Arbeit zeigt sich das Problem: Die Seite wird bei vielen gleichzeitigen Nutzern quälend langsam. Jedes neue Feature erfordert Änderungen an dutzenden Stellen. Und die Codebasis? Ein undurchschaubares Chaos.

Wie hätte das Team diese Probleme vermeiden können?

Die Antwort: Qualitätsgetriebener Entwurf. Wer von Anfang an Wartbarkeit, Performance und Skalierbarkeit in den Mittelpunkt stellt, spart sich später teure Nachbesserungen und frustrierende Debugging-Sessions.

Lernziele

Nach dieser Lerneinheit kannst du:

  • Die Prinzipien des Quality-Driven Design erklären und anwenden
  • Architekturmuster (Layered, Microservices, Event-Driven, Client-Server) unterscheiden und situativ auswählen
  • Methoden zur Architekturbewertung (ATAM, SAAM, CBAM) beschreiben
  • Code Smells erkennen und durch Refactoring beheben

Überleitung

Das Chaos der E-Commerce-Plattform hätte sich vermeiden lassen, wenn das Team von Beginn an die Qualität in den Mittelpunkt gestellt hätte.

Genau das macht der qualitätsgetriebene Entwurf. Schauen wir uns an, was dahinter steckt.

Was ist Quality-Driven Design?

Quality-Driven Design (QDD) ist ein Ansatz, bei dem Qualitätsmerkmale wie Wartbarkeit, Performance und Sicherheit von Anfang an in den Entwurfsprozess einbezogen werden.

Statt Qualität erst am Ende zu prüfen, wird sie zum Leitprinzip jeder Architekturentscheidung.

Der Grundgedanke: Fehler im Entwurf sind später teuer zu beheben. Je früher Qualitätsanforderungen berücksichtigt werden, desto kostengünstiger und stabiler wird die Software.

Schlüsselprinzipien von QDD

Quality-Driven Design folgt vier zentralen Prinzipien, die den gesamten Entwicklungsprozess leiten:

PrinzipBeschreibung
Frühzeitige IntegrationQualitätsanforderungen werden von Projektbeginn an gesammelt und priorisiert
Systematische BewertungDie Architektur wird regelmäßig geprüft, ob sie die Qualitätsziele erfüllt
Iteratives VerfeinernErkenntnisse aus Bewertungen fließen in Verbesserungen ein
Best Practices nutzenBewährte Muster und Techniken werden gezielt eingesetzt

Diese Prinzipien wirken zusammen: Du startest mit klaren Qualitätszielen, überprüfst regelmäßig den Fortschritt und passt die Architektur bei Bedarf an.

Architekturmuster - Überblick

Architekturmuster sind bewährte Lösungsansätze für wiederkehrende Probleme beim Systemdesign. Sie definieren, wie Komponenten organisiert und miteinander verbunden werden.

Warum sind sie wichtig?

  • Sie fördern die Kommunikation im Team durch gemeinsames Vokabular
  • Sie erleichtern Architekturentscheidungen
  • Sie unterstützen gezielt bestimmte Qualitätsattribute

Die vier wichtigsten Muster schauen wir uns jetzt an.

Layered Architecture (Schichtenarchitektur)

Die Schichtenarchitektur trennt Verantwortlichkeiten in klar abgegrenzte Schichten:

SchichtAufgabeBeispiel
PräsentationsschichtBenutzeroberflächeHTML/CSS, React, Angular
GeschäftslogikschichtFachliche RegelnBestellvalidierung, Preisberechnung
DatenzugriffsschichtDatenbankzugriffSQL-Queries, ORM

Stärken: Gute Wartbarkeit, einfaches Testen einzelner Schichten, klare Struktur

Einschränkung: Bei sehr großen Systemen kann die strikte Schichtung zu Performance-Overhead führen.

Microservices Architecture

Bei Microservices wird eine Anwendung in kleine, unabhängige Services aufgeteilt. Jeder Service ist für eine spezifische Geschäftsfunktion zuständig.

Beispiel E-Commerce:

  • Produkt-Service: Verwaltet Produktkatalog
  • Warenkorb-Service: Verwaltet Warenkörbe
  • Bestell-Service: Verarbeitet Bestellungen
  • User-Service: Authentifizierung und Profile

Stärken: Hohe Skalierbarkeit, unabhängiges Deployment, Technologievielfalt möglich

Einschränkung: Höhere Komplexität bei Kommunikation, Monitoring und Debugging.

Event-Driven Architecture

Die ereignisgesteuerte Architektur organisiert Software um die Verarbeitung von Events (Ereignissen).

Ablauf:

  1. Ein Producer löst ein Event aus (z.B. “Bestellung aufgegeben”)
  2. Das Event wird an einen Event Bus gesendet
  3. Consumer reagieren auf das Event (z.B. Lager aktualisieren, Email senden)

Stärken: Hohe Reaktionsfähigkeit, lose Kopplung zwischen Komponenten, gute Skalierbarkeit

Einschränkung: Komplexeres Debugging, da Ereignisse asynchron verarbeitet werden.

Client-Server Architecture

Die klassische Client-Server-Architektur trennt zwischen Anfragenden (Clients) und Dienstanbietern (Servern).

Typisches Szenario:

  • Client: Webbrowser, Mobile App
  • Server: Webserver, API-Server, Datenbankserver

Stärken: Klare Rollenverteilung, zentralisierte Datenhaltung, einfache Skalierung durch mehr Server

Einschränkung: Server ist Single Point of Failure, wenn nicht redundant ausgelegt.

Architekturmuster - Zusammenfassung

Die vorgestellten Architekturmuster haben jeweils ihre Stärken und eignen sich für unterschiedliche Szenarien. Die Wahl des richtigen Musters hängt von den Qualitätszielen deines Projekts ab.

MusterPrimäres QualitätszielTypischer Einsatz
LayeredWartbarkeit, TestbarkeitKlassische Webanwendungen, Unternehmenssoftware
MicroservicesSkalierbarkeit, UnabhängigkeitGroße, verteilte Systeme, Cloud-native Apps
Event-DrivenReaktionsfähigkeit, EntkopplungIoT, Echtzeit-Systeme, Message-Verarbeitung
Client-ServerZentralisierung, SkalierbarkeitWeb-Apps, Datenbank-Anwendungen

Wichtig: In der Praxis werden Muster oft kombiniert. Eine E-Commerce-Plattform könnte Microservices mit Event-Driven-Kommunikation nutzen.

Warum Architekturen bewerten?

Eine Architektur zu entwerfen ist nur der erste Schritt. Um sicherzustellen, dass sie die Qualitätsziele erfüllt, muss sie systematisch bewertet werden.

Gründe für Architekturbewertung:

  • Risiken frühzeitig erkennen
  • Trade-offs zwischen Qualitätszielen verstehen
  • Verbesserungspotenziale identifizieren
  • Stakeholder-Erwartungen abgleichen

Die drei etabliertesten Methoden sind ATAM, SAAM und CBAM.

ATAM - Architecture Tradeoff Analysis Method

ATAM wurde vom Software Engineering Institute (Carnegie Mellon University) entwickelt. Der Name verrät den Kern: Es geht um Trade-offs zwischen Qualitätsattributen.

Jede Architekturentscheidung hat Vor- und Nachteile. ATAM macht diese sichtbar.

Beispiel Trade-off: Mehr Verschlüsselung erhöht die Sicherheit, kann aber die Performance reduzieren.

ATAM hilft, solche Konflikte zu identifizieren und bewusst zu entscheiden.

⏳ Lädt Dataview-Inhalt...

Die 9 Schritte der ATAM-Methode

ATAM wird typischerweise in zwei Phasen durchgeführt. Phase 1 findet mit dem Kernteam statt und dient der ersten Analyse. Phase 2 bezieht weitere Stakeholder ein und erweitert die Perspektive.

Warum zwei Phasen? In Phase 1 verstehen die Architekten das System im Detail. In Phase 2 bringen Stakeholder neue Szenarien ein, die das Team möglicherweise übersehen hat. Diese Kombination stellt sicher, dass sowohl technische als auch geschäftliche Aspekte berücksichtigt werden.

PhaseSchrittBeschreibung
Phase 11. ATAM vorstellenMethode den Stakeholdern erklären
2. Business DriversGeschäftsziele und Anforderungen präsentieren
3. Architektur vorstellenArchitekt präsentiert das Design
4. Architekturansätze identifizierenVerwendete Muster und Taktiken dokumentieren
5. Utility Tree erstellenQualitätsattribute priorisieren und Szenarien zuordnen
6. Analyse (erste Runde)Szenarien gegen Architektur testen
Phase 27. Weitere Szenarien sammelnMit mehr Stakeholdern neue Szenarien erarbeiten
8. Analyse (zweite Runde)Erweiterte Analyse
9. Ergebnisse präsentierenRisiken, Trade-offs, Empfehlungen dokumentieren

Typischer Aufwand: Phase 1 dauert etwa einen Tag, Phase 2 zwei weitere Tage. Für kleinere Projekte kann ATAM auch in verkürzter Form durchgeführt werden.

Architektur Trade-offs - Security vs. Performance

Beispiel 1: Security vs. Performance

  • Szenario: E-Commerce mit hohem Sicherheitsbedarf
  • Trade-off: Jede Anfrage verschlüsseln und authentifizieren
  • Vorteil: Maximale Sicherheit
  • Nachteil: Jede Anfrage dauert 50-100ms länger
  • Lösung: Kritische Operationen (Zahlung) stark absichern, öffentliche Inhalte (Produktliste) weniger

Architektur Trade-offs - Konsistenz vs. Verfügbarkeit

Beispiel 2: Konsistenz vs. Verfügbarkeit

  • Szenario: Verteilte Datenbank
  • Trade-off: Immer aktuelle Daten vs. System auch bei Netzwerkproblemen verfügbar
  • Lösung: Für Likes/Kommentare reicht “Eventual Consistency”, für Zahlungen braucht man starke Konsistenz

Das ist das bekannte CAP-Theorem: Consistency, Availability, Partition Tolerance - nur zwei von drei sind gleichzeitig erreichbar.

SAAM - Software Architecture Analysis Method

SAAM war die erste dokumentierte Architektur-Analysemethode (Mitte der 1990er) und ist der Vorläufer von ATAM. Der Fokus liegt auf der Bewertung der Modifizierbarkeit einer Architektur.

SAAM nutzt szenariobasierte Evaluierung und unterscheidet zwei Arten von Szenarien:

  • Direct Scenarios: Anforderungen, die die Architektur direkt unterstützt (keine Änderungen nötig)
  • Indirect Scenarios: Anforderungen, die Architektur-Änderungen erfordern

Beispiel:

  • Direct: “Produkte anzeigen” - funktioniert bereits
  • Indirect: “Multi-Währungs-Support” - erfordert Architekturanpassungen

Je mehr Indirect Scenarios es gibt, desto geringer ist die Modifizierbarkeit der Architektur. SAAM hilft damit, potenzielle Änderungsaufwände früh zu erkennen.

CBAM - Cost Benefit Analysis Method

CBAM erweitert ATAM um eine wirtschaftliche Perspektive. Architekturentscheidungen werden nach Kosten und Nutzen bewertet.

Kernfragen:

  • Was kostet die Umsetzung einer Architekturvariante?
  • Welchen geschäftlichen Nutzen bringt sie?
  • Wie ist der Return on Investment (ROI)?

Anwendung: CBAM wird nach einer ATAM-Analyse eingesetzt, wenn mehrere Architekturoptionen zur Auswahl stehen und die Investition begründet werden muss.

Was ist Refactoring?

Refactoring ist ein systematischer Prozess zur Verbesserung des Codes, ohne sein externes Verhalten zu ändern.

Der Code macht nachher genau dasselbe wie vorher, aber er ist sauberer, verständlicher und wartbarer.

Ziele:

  • Bessere Lesbarkeit für andere Entwickler
  • Einfachere Wartung und Fehlersuche
  • Vorbereitung für neue Features
  • In manchen Fällen: Performance-Verbesserung

Wann sollte Refactoring durchgeführt werden?

Refactoring ist keine einmalige Aktion, sondern ein kontinuierlicher Prozess. Die Frage ist nicht ob, sondern wann und wie oft.

ZeitpunktBegründung
Bei Code SmellsWenn der Code Anzeichen von schlechtem Design zeigt
Vor ErweiterungenBevor neue Features eingebaut werden, die Codebasis aufräumen
Nach FertigstellungNach einem Feature den Code “polieren”
RegelmäßigAls Teil der normalen Entwicklung, nicht als separates Projekt

Wichtig: Vor dem Refactoring müssen Tests existieren, die das korrekte Verhalten sicherstellen. Ohne Tests weißt du nicht, ob du beim Umbau etwas kaputt gemacht hast.

Code Smells - Überblick

Code Smells sind Hinweise auf potenzielle Probleme im Code. Sie sind keine Fehler im eigentlichen Sinne, aber Warnsignale für schlechtes Design, das langfristig zu Problemen führen kann.

Der Begriff stammt von Kent Beck und wurde durch Martin Fowlers Buch “Refactoring” populär.

Die wichtigsten Code Smells, die du kennen solltest:

  • Duplicated Code - Gleicher Code an mehreren Stellen
  • Long Method - Methoden mit zu vielen Zeilen
  • Large Class - Klassen mit zu vielen Verantwortlichkeiten
  • Magic Numbers - Hardcodierte Zahlen ohne Erklärung

Schauen wir uns einige davon genauer an.

Code Smell - Duplicated Code

Problem: Dieselbe Logik existiert an mehreren Stellen. Änderungen müssen überall nachgezogen werden.

Beispiel: Email-Validierung in zwei Methoden:

// In der Registrierung:
public void registriere(String email, String passwort) {
    if (email == null || email.indexOf("@") < 0) {
        throw new IllegalArgumentException("Ungültige Email");
    }
    // ... weitere Logik
}
 
// Im Profil-Update (identische Prüfung):
public void aktualisiereEmail(String neueEmail) {
    if (neueEmail == null || neueEmail.indexOf("@") < 0) {
        throw new IllegalArgumentException("Ungültige Email");
    }
    // ... weitere Logik
}

Erklärung des Codes

Dieser Code zeigt das Problem von dupliziertem Code.

  1. Die Validierungslogik ist identisch: In beiden Methoden wird exakt dieselbe Prüfung durchgeführt: email == null || email.indexOf("@") < 0. Das prüft, ob die Email leer ist oder kein @-Zeichen enthält.

  2. Warum ist das problematisch? Stell dir vor, du möchtest die Validierung verbessern (z.B. auch auf einen Punkt nach dem @ prüfen). Du musst jetzt an beiden Stellen ändern. Bei größeren Projekten mit dutzenden solcher Stellen vergisst man leicht eine.

  3. Die Lösung: Extrahiere die Validierung in eine eigene Methode:

private void validiereEmail(String email) {
    if (email == null || email.indexOf("@") < 0) {
        throw new IllegalArgumentException("Ungültige Email");
    }
}

Jetzt rufst du nur noch validiereEmail(email) auf. Änderungen sind zentral an einer Stelle möglich.

Code Smell - Long Method und Magic Numbers

Long Method

Problem: Eine Methode macht zu viel und ist schwer zu verstehen.

Faustregel: Methoden mit mehr als 20-30 Zeilen oder mehr als einer Verantwortlichkeit sind verdächtig.

Lösung: In kleinere, fokussierte Methoden aufteilen (Extract Method).


Magic Numbers

Problem: Hardcodierte Zahlen ohne Erklärung.

// BAD: Was bedeutet 1?
if (user.getStatus() == 1) {
    aktiviereBenutzer(user);
}
 
// GOOD: Konstante mit sprechendem Namen
public static final int STATUS_ACTIVE = 1;
if (user.getStatus() == STATUS_ACTIVE) {
    aktiviereBenutzer(user);
}

Erklärung des Codes

Dieser Code zeigt das Problem der “Magic Numbers” und deren Lösung.

  1. if (user.getStatus() == 1) (BAD): Im ersten Beispiel wird der Status des Benutzers mit der Zahl 1 verglichen. Das Problem: Niemand weiß auf Anhieb, was 1 bedeutet. Ist das “aktiv”? “Gesperrt”? “Premium”? Man muss raten oder in der Dokumentation suchen.

  2. public static final int STATUS_ACTIVE = 1; (GOOD): Hier wird eine Konstante definiert. Das Schlüsselwort final bedeutet, dass der Wert nicht mehr geändert werden kann. static macht die Konstante klassenübergreifend verfügbar.

  3. if (user.getStatus() == STATUS_ACTIVE): Jetzt ist sofort klar, was geprüft wird: Ist der Benutzer aktiv? Der Code dokumentiert sich selbst.

Warum ist das wichtig? Bei “Magic Numbers” muss man raten, was die Zahl bedeutet. Konstanten dokumentieren sich selbst und verhindern Fehler bei Änderungen.

Refactoring-Technik - Extract Method (Vorher)

Die häufigste Refactoring-Technik: Code-Blöcke in eigene Methoden auslagern.

Vorher: Eine Methode mit mehreren Verantwortlichkeiten:

public void verarbeiteBestellung(Bestellung bestellung) {
    // Validierung
    if (bestellung.getProdukte().isEmpty()) {
        throw new IllegalArgumentException("Keine Produkte");
    }
    if (bestellung.getKunde() == null) {
        throw new IllegalArgumentException("Kein Kunde");
    }
 
    // Preisberechnung
    double summe = 0;
    for (Produkt p : bestellung.getProdukte()) {
        summe += p.getPreis() * p.getMenge();
    }
 
    // Speicherung
    database.save(bestellung);
    emailService.sendeBestaetigung(bestellung);
}

Erklärung des Codes

Diese Methode macht drei verschiedene Dinge:

  1. Validierung (Zeilen 2-7): Zuerst wird geprüft, ob die Bestellung gültig ist. Hat sie Produkte? Ist ein Kunde zugeordnet? Bei Fehlern wird eine Exception geworfen.

  2. Preisberechnung (Zeilen 9-12): Eine Schleife iteriert durch alle Produkte und berechnet die Gesamtsumme: Preis mal Menge für jedes Produkt.

  3. Speicherung (Zeilen 14-15): Die Bestellung wird in der Datenbank gespeichert und eine Bestätigungsmail wird versendet.

Das Problem: Diese Methode verletzt das Single Responsibility Principle. Sie hat drei Verantwortlichkeiten. Wenn sich die Preisberechnung ändert (z.B. Rabatte), muss man in dieser großen Methode suchen. Tests werden kompliziert, weil man nicht einzelne Teile isoliert testen kann.

Extract Method - Nachher

Nachher: Dieselbe Funktionalität, aber aufgeteilt in fokussierte Methoden:

public void verarbeiteBestellung(Bestellung b) {
    validiereBestellung(b);
    double summe = berechneGesamtpreis(b);
    b.setGesamtpreis(summe);
    speichereBestellung(b);
}
 
private void validiereBestellung(Bestellung b) {
    if (b.getProdukte().isEmpty())
        throw new IllegalArgumentException("Keine Produkte");
    if (b.getKunde() == null)
        throw new IllegalArgumentException("Kein Kunde");
}
 
private double berechneGesamtpreis(Bestellung b) {
    double summe = 0;
    for (Produkt p : b.getProdukte())
        summe += p.getPreis() * p.getMenge();
    return summe;
}
 
private void speichereBestellung(Bestellung b) {
    database.save(b);
    emailService.sendeBestaetigung(b);
}

Erklärung des Codes

Der Code wurde in vier separate Methoden aufgeteilt:

  1. verarbeiteBestellung() (Zeilen 1-6): Die Hauptmethode ist jetzt eine “Koordinator”-Methode. Sie ruft nur noch andere Methoden auf und liest sich wie eine Checkliste: validieren, berechnen, Preis setzen, speichern.

  2. validiereBestellung() (Zeilen 8-12): Diese Methode hat genau eine Aufgabe: Prüfen, ob die Bestellung gültig ist. Sie ist private, weil sie nur intern verwendet wird.

  3. berechneGesamtpreis() (Zeilen 14-19): Berechnet nur den Gesamtpreis und gibt ihn als double zurück. Durch den Rückgabewert ist klar, was die Methode macht.

  4. speichereBestellung() (Zeilen 21-24): Kümmert sich um die Persistenz: Datenbank-Speicherung und Email-Versand.

Die Vorteile:

  • Lesbarkeit: Die Hauptmethode erklärt sich selbst.
  • Testbarkeit: Jede Methode kann einzeln getestet werden.
  • Wartbarkeit: Änderungen an der Preisberechnung betreffen nur berechneGesamtpreis().
  • Wiederverwendbarkeit: berechneGesamtpreis() könnte auch an anderer Stelle genutzt werden.
⏳ Lädt Dataview-Inhalt...

Zusammenfassung

Zusammenfassung

Quality-Driven Design stellt Qualitätsmerkmale wie Wartbarkeit, Performance und Sicherheit von Anfang an in den Mittelpunkt des Entwurfsprozesses. Die vier Schlüsselprinzipien - frühzeitige Integration, systematische Bewertung, iteratives Verfeinern und Nutzung von Best Practices - sorgen dafür, dass Qualität kein nachträglicher Gedanke ist, sondern jede Architekturentscheidung leitet.

Architekturmuster bieten bewährte Lösungen für wiederkehrende Designprobleme:

MusterStärkeTypischer Einsatz
LayeredWartbarkeit, klare TrennungKlassische Webanwendungen
MicroservicesSkalierbarkeit, unabhängiges DeploymentCloud-native, große Systeme
Event-DrivenReaktionsfähigkeit, lose KopplungEchtzeit-Systeme, IoT
Client-ServerZentralisierung, einfache SkalierungWeb-Applikationen

Architekturbewertung macht Trade-offs sichtbar und hilft, Risiken frühzeitig zu erkennen. ATAM identifiziert Konflikte zwischen Qualitätszielen durch systematische Szenario-Analyse. SAAM bewertet die Modifizierbarkeit anhand von Direct und Indirect Scenarios. CBAM ergänzt die wirtschaftliche Perspektive mit Kosten-Nutzen-Analyse.

Refactoring verbessert die Codequalität kontinuierlich, ohne das externe Verhalten zu ändern. Code Smells wie Duplicated Code, Long Methods und Magic Numbers sind Warnsignale, die auf Refactoring-Bedarf hindeuten. Die Extract-Method-Technik ist das wichtigste Werkzeug, um große Methoden in fokussierte, wiederverwendbare Einheiten aufzuteilen und so das Single Responsibility Principle umzusetzen.

Ausblick

In der nächsten Lerneinheit Implementierung und Coding lernst du, wie du Coding Standards einsetzt, Code durch statische Analyse prüfst und Code Reviews effektiv durchführst.