Zum Inhalt springen

Aufgabe 08 - Jahresprojekt Sprint 2 - SRP, OCP und LSP

Zu Zen-Modus wechseln
  • Du benennst die Gründe zur Änderung einer Klasse und teilst sie nach dem Single Responsibility Principle so, dass die Logik ohne Infrastruktur testbar ist.
  • Du erkennst eine if-Kette über einen Typ als Verstoß gegen das Open-Closed-Prinzip und ersetzt sie durch eine Strategie hinter einem Interface.
  • Du schreibst den Vertrag eines Obertyps auf und erkennst Untertypen, die mehr verlangen oder weniger liefern.
  • Du setzt SRP, OCP und LSP im Code deines Jahresprojekts um und dokumentierst die Entscheidungen.

Zweite Woche von Sprint 2. Kapitel 12 und 13 behandeln die ersten drei Buchstaben von SOLID: S (ein Grund zur Änderung), O (Neues als neue Klasse, nicht als neuer Zweig) und L (Untertypen halten das Versprechen ihres Obertyps). In Aufgabe 07 habt ihr im Design-Review Stellen gefunden, an denen eine Änderung Kreise zieht. Heute räumt ihr die erste davon auf, mit einem Test, der zeigt, dass es danach leichter ist.

Die Kata: Hörbuch-Abo-Abrechnung. Der Startcode:

interface Track {
title: string;
minutes: number;
}
class SubscriptionBill {
constructor(private readonly tracks: Track[], private readonly plan: string) {}
totalCents(): number {
const minutes = this.tracks.reduce((sum, e) => sum + e.minutes, 0);
if (this.plan === "basic") return minutes * 2;
if (this.plan === "pro") return Math.min(minutes * 2, 900);
if (this.plan === "school") return 0;
throw new Error(`unknown plan ${this.plan}`);
}
toHtml(): string {
return `<h1>Rechnung</h1><p>${(this.totalCents() / 100).toFixed(2)} EUR</p>`;
}
sendTo(address: string): void {
console.log(`mail to ${address}: ${this.toHtml()}`);
}
}
class Player {
// contract: never throws; returns false if the track cannot be played
play(track: Track): boolean {
return track.minutes > 0;
}
}
class OfflinePlayer extends Player {
play(track: Track): boolean {
if (!track.title.endsWith(".mp3")) throw new Error("only downloaded mp3 files");
return true;
}
}

Dafür brauchst du:

  • Kapitel 12 Entwurfsmuster I – SOLID: S und O und Kapitel 13 Entwurfsmuster I – SOLID: L.
  • Ein TypeScript-Projekt, das mit npx tsx läuft (für die Kata), node:assert genügt für die Prüfungen.
  • Euer Design-Review aus Aufgabe 07.

Projektordner anlegen siehe https://qiz.at/projektordner

  • Lies vorher kurz https://qiz.at/assignmentsabgabe durch.
  • Nur vollständige Abgaben (siehe https://qiz.at/rules) können positiv bewertet werden.
  • Abgabe- und Testdetails siehe https://qiz.at/assignmentsabgabe
  • Abgegeben wird dein Projektordner mit der Kata (Code und README.md) und den Links auf Issues, Merge Requests und Abschnitte im docs/design-review.md des Jahresprojekts. Bezeichner und Kommentare im Code sind Englisch.
  • Wer was beigetragen hat, zeigen Board, Merge Requests und /spend-Einträge (Kapitel 07).
  • Kata: Nenne in der README.md die Gründe, aus denen sich SubscriptionBill ändern müsste, mit je einer konkreten Änderungsanfrage. Teile die Klasse so, dass jede Klasse genau einen Grund zur Änderung hat, und ersetze die if-Kette über den Tarif durch ein Interface PricingPlan mit einer Klasse je Tarif. Prüfe die Berechnung mit zwei assert-Aufrufen, ohne dass dabei HTML entsteht oder etwas ausgegeben wird.
  • Schreibe den Vertrag von Player.play (Vorbedingung, Nachbedingung) auf und begründe in einem Satz, ob OfflinePlayer mehr verlangt oder weniger liefert.

Tipp: SRP heißt „ein Grund zur Änderung“, nicht „eine Methode“. Committe vor dem neuen Tarif, dann zeigt git status, ob ein neuer Tarif wirklich nur eine neue Datei braucht.

Vollständig, wenn:

  • mindestens drei Gründe zur Änderung mit je einer Änderungsanfrage genannt sind und nach der Aufteilung jede Klasse genau einen Grund hat,
  • die Tarife hinter PricingPlan stehen und keine Verzweigung über den Tarif mehr existiert,
  • die Berechnung mit zwei Prüfungen ohne HTML und ohne Ausgabe getestet ist,
  • der Vertrag von play steht und OfflinePlayer richtig als Untertyp eingeordnet ist, der mehr verlangt.

Wie unter Allgemein.

Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.

Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieser Datei (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):

  • SRP im Projekt: Wähle eine Klasse, Funktion oder Komponente eures Projekts mit mindestens zwei Gründen zur Änderung (zum Beispiel eine Komponente, die Daten lädt, prüft und anzeigt, oder eine Route, die rechnet und speichert). Lege dafür ein Issue an und teile sie in einem Merge Request so, dass die Fachlogik ohne Oberfläche, Netzwerk und Datenbank testbar wird. Schreibe mindestens einen Test, der genau diese Logik prüft.
  • LSP im Projekt: Suche alle Stellen mit extends oder implements und alle instanceof-Prüfungen. Beurteile jede Stelle in einer Tabelle im Design-Review (Datei und Zeile, Vertrag des Obertyps, verlangt mehr oder liefert weniger?). Hat euer Projekt keine Vererbung, prüfe zwei Implementierungen desselben Interfaces oder Typs gegen dessen Erwartungen.
  • Ergänze im Design-Review einen Abschnitt SRP, OCP, LSP: Vorher-Nachher der geteilten Stelle (Gründe zur Änderung vorher und nachher), Link auf den Merge Request.

Tipp: Für Logik, die ihr aus einer Komponente herauslöst, eignet sich eine Datei in eurer Fachlogik-Schicht. Wenn der Test die Oberfläche starten muss, ist die Trennung noch nicht gelungen.

Vollständig, wenn:

  • der Merge Request eine Stelle mit mindestens zwei Gründen zur Änderung so teilt, dass jede neue Einheit einen Grund hat, und mindestens ein Test die herausgelöste Logik ohne Infrastruktur prüft,
  • jede Stelle mit extends, implements oder instanceof mit Datei und Zeile gegen den Vertrag beurteilt ist,
  • der Abschnitt im Design-Review Vorher und Nachher nennt und den Merge Request verlinkt.

Wie unter Allgemein.

Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.

Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieser Datei (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):

  • OCP im Projekt: Suche eine if- oder switch-Kette über einen Typ oder eine Art (Medientyp, Rolle, Status, Format). Ersetze sie durch eine Strategie hinter einem Interface, sodass eine neue Variante nur eine neue Klasse und ihre Auswahl braucht. Weise mit git diff --stat nach, dass eine zusätzliche Variante keine bestehende Variante berührt. Gibt es keine solche Kette, begründe im Design-Review, warum, und nenne die Stelle, an der sie als Nächstes entstehen würde.
  • Beantworte im Design-Review in zwei Sätzen: Wer wählt jetzt die passende Strategie aus, und wo steht das im Code?

Tipp: Die offene Frage „Wer wählt aus?“ ist kein Fehler deiner Lösung. Kapitel 16 beantwortet sie mit der Factory.

Vollständig, wenn:

  • die Kette durch Strategien hinter einem Interface ersetzt ist und der Diff einer zusätzlichen Variante nur neue Dateien und die Auswahlstelle zeigt (oder das Fehlen einer Kette begründet ist),
  • die Auswahlstelle mit Datei benannt ist.

Wie unter Allgemein.

Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.

Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieser Datei (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):

  • Schreibe einen Vertragstest für ein Interface oder einen Obertyp eures Projekts: eine Testfunktion, die gegen den Obertyp geschrieben ist und mit jeder Implementierung laufen muss. Lass sie gegen alle Implementierungen laufen und behebe einen gefundenen Vertragsbruch, ohne den Vertrag aufzuweichen.
  • Nimm eine echte Änderungsanfrage aus eurem Backlog, die eure Strategie aus PlusPlus (oder eine andere Abstraktion) nicht abfängt, zum Beispiel weil die Signatur die nötige Information gar nicht bekommt. Erkläre in drei bis vier Sätzen, warum das Open-Closed-Prinzip nicht jede Änderung abfangen kann und was das für die Wahl der Abstraktion bedeutet.

Tipp: Kapitel 13 weist darauf hin, dass auch Attrappen in Tests den Vertrag halten müssen. Ein Vertragstest ist der einfachste Weg, das zu prüfen.

Vollständig, wenn:

  • der Vertragstest gegen den Obertyp geschrieben ist, mit allen Implementierungen läuft und ein Vertragsbruch behoben oder begründet ausgeschlossen ist,
  • die Erklärung an einer echten Änderungsanfrage zeigt, welche Information der Abstraktion fehlt.

Wie unter Allgemein.

Durchschnittlich bis Wesentlich_vollständig: ca. 70 min (5 min Board-Check, 25 min Kata, 40 min SRP und LSP im Projekt)

GAM

CC-BY-SA

https://qiz.at/rules