Übungsaufgaben zu einfachem Code: KISS, YAGNI, DRY und Beware of Premature Optimization
https://bildung.social/@oerinformatik/
https://oer-informatik.de/einfacher-code-uebungsaufgaben
Clean Code, KISS, YAGNI, DRY, Premature Optimization, Doppelter Code, Rule of Three, Zielkonflikt, Übungsaufgaben, Prüfungsvorbereitung
tl/dr; (ca. 69 min Lese- und Bearbeitungszeit): Fünf Übungsaufgaben zu einfachem Code: Du ordnest Verstöße gegen KISS, YAGNI, DRY und Beware of Premature Optimization in der Mitfahr-App Mitfahrlabor zu, unterscheidest echte Wissensdopplung von zufälliger Ähnlichkeit, räumst den Bestellabschluss der CSB-Rad-Kata von Stand 02 zu Stand 03 auf, führst eine kopierte Kostenbeitragsberechnung zusammen und wägst zum Schluss DRY gegen die Kopplung zweier Teams ab [AI-Assisted]. (Zuletzt geändert am 09.11.2026)
Disclaimer: Teile des Textes stammen von einer KI. Inhalte sind nicht automatisch zuverlässig; überprüfe Fakten und zitiere vertrauenswürdige Quellen.
Diese Übungsaufgaben gehören zum Artikel Einfach bleiben: KISS, YAGNI, DRY und Beware of Premature Optimization.
Hinweise zur Bearbeitung: Bis zur Teil-1-Prüfung der IT-Berufe solltest Du die Basiskompetenz beherrschen, also Aufgabe 1.1 und Aufgabe 1.2. Als FIA musst Du zur Teil-2-Prüfung Aufgaben auf Kernkompetenz-Ebene lösen können, das sind Aufgabe 2.1 und Aufgabe 2.2. Aufgaben auf Expertise-Ebene (Aufgabe 4.1) gehen darüber hinaus. Die Codeausschnitte stehen in Java und Python; wähle die Sprache, die Du gerade lernst, und schreib Deine Lösung in dieser Sprache. Achte auf die Operatoren: „Nenne“ verlangt Stichworte ohne Erklärung, „Ordne zu“ verlangt die Zuordnung mit einem Satz Begründung, „Beurteile“ und „Nimm Stellung“ verlangen eine Aussage mit Begründung, „Baue um“ und „Schreibe“ verlangen lauffähigen Code. Schreib Deine Lösung zuerst vollständig auf, bevor Du die Beispiellösung aufklappst.
Du möchtest eigene Aufgaben zum Üben von einem KI-Sprachmodell generieren lassen?
Kopiere den Text in ein KI-Sprachmodell Deiner Wahl und passe Kompetenzstufe, Anzahl, Bearbeitungszeit und Szenario bei Bedarf an. Wie die Aufgaben aufgebaut sind, beschreibt der Artikel Wie sind die Aufgaben aufgebaut?. Prüfe die Ergebnisse mit dem Lehrartikel: KI-Sprachmodelle machen Fehler.
Erstelle neue Übungsaufgaben zum Thema „Einfach bleiben: KISS, YAGNI,
DRY und Beware of Premature Optimization“.
Bauplan: Halte Dich an Aufbau, Kompetenzstufen, Operatoren,
Anforderungsbereiche, Punkte und Zeitschätzung, die im Artikel
https://oer-informatik.de/aufgaben-aufbau beschrieben sind.
Fachliche Grundlage:
- https://oer-informatik.de/einfacher-code
- https://oer-informatik.de/lesbarer-code
- https://oer-informatik.de/clean-code
- https://oer-informatik.de/refactoring
- https://oer-informatik.de/unit-tests
- Stilbeispiel: https://oer-informatik.de/einfacher-code-uebungsaufgaben
Die Aufgaben sollen Inhalte zu diesen Schlagwörtern abdecken:
- Clean Code
- KISS
- YAGNI
- DRY
- Premature Optimization
- Doppelter Code
- Rule of Three
- Zielkonflikt
Kompetenzstufe: Basiskompetenz (alternativ: Kernkompetenz, Profilkompetenz, Expertise)
Anzahl: 1 Aufgabe mit Teilaufgaben
Bearbeitungszeit in Minuten ca.: <5 bis 30 min>
Szenario: eines der Szenarien aus dem Bauplan
Vorgehen:
1. Lies zuerst die verlinkten Artikel. Kannst Du keine Webseiten
abrufen, sag mir das, statt zu raten.
2. Steht bei einer Angabe noch ein Platzhalter in <…>, frag mich
zuerst danach. Richte Punkte und Zahl der Teilaufgaben nach der
Bearbeitungszeit (ca. 0,9 min je Punkt).
3. Gib je Aufgabe Titel mit Kompetenzstufe, Ausgangssituation und
Teilaufgaben mit Operator und Punktzahl aus, dazu die geschätzte
Bearbeitungszeit.
4. Gib die Beispiellösungen mit Punkteverteilung erst aus, wenn ich
danach frage.
Aufgabe 1.1: Prinzipien in der Fahrtenverwaltung erkennen (Basiskompetenz)
Im Projektteam der Mitfahr-App Mitfahrlabor am DBH-Berufskolleg prüft Oksana Melnyk vor dem nächsten Release die Klasse Fahrtenverwaltung. Alle Tests sind grün, aber vier Stellen (Ausschnitt A bis D) kommen ihr unnötig kompliziert vor. Beträge speichert die App in Cent, Strecken in Metern; der Kostenbeitrag ist auf 0,10 € je km und Person begrenzt.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Fahrtenverwaltung {
private static final Map<String, Integer> ENTFERNUNG_METER = Map.of(
"Gütersloh", 18600, "Herford", 17900, "Bad Salzuflen", 19400);
private final List<String> angebote = new ArrayList<>();
// Ausschnitt A: Entfernungen zwischenspeichern, damit es schneller geht
private final Map<String, Integer> entfernungsCache = new HashMap<>();
public int entfernungMeter(String ort) {
if (!entfernungsCache.containsKey(ort)) {
entfernungsCache.put(ort, ENTFERNUNG_METER.get(ort));
}
return entfernungsCache.get(ort);
}
// Ausschnitt B
public void bieteFahrtAn(String haltepunkt, String abfahrt, int freiePlaetze, boolean mitRueckfahrt, String waehrung) {
angebote.add(haltepunkt + ", " + abfahrt + " Uhr, " + freiePlaetze + " Plätze");
}
// Ausschnitt C
public boolean darfMitfahren(int alter, boolean einwilligungLiegtVor) {
return alter >= 18 ? true : (einwilligungLiegtVor ? true : false);
}
// Ausschnitt D
public int beitragHinfahrtCent(int streckeMeter, int wunschsatzCentJeKm) {
return (streckeMeter * Math.min(wunschsatzCentJeKm, 10) + 500) / 1000;
}
public int beitragRueckfahrtCent(int streckeMeter, int wunschsatzCentJeKm) {
return (streckeMeter * Math.min(wunschsatzCentJeKm, 10) + 500) / 1000;
}
}a) Ordne jedem der Ausschnitte A bis D das Prinzip zu, gegen das er am deutlichsten verstößt (KISS, YAGNI, DRY oder Beware of Premature Optimization), und begründe Deine Zuordnung jeweils in einem Satz. (8 Punkte)
| Ausschnitt | Prinzip | Begründung |
|---|---|---|
| A | ||
| B | ||
| C | ||
| D |
| Ausschnitt | Prinzip | Begründung |
|---|---|---|
| A | Beware of Premature Optimization | Der Zwischenspeicher speichert das Ergebnis eines Map-Zugriffs in einer zweiten Map; das spart nichts, ist nicht gemessen und macht die Klasse um einen Zustand reicher. |
| B | YAGNI | Die Parameter mitRueckfahrt und waehrung werden nie gelesen; sie sind „für später“ eingebaut, und jeder Aufruf muss sie trotzdem angeben. |
| C | KISS | Zwei verschachtelte Bedingungsoperatoren mit true/false drücken umständlich eine einfache Oder-Verknüpfung aus. |
| D | DRY | Die Regel „Wunschsatz auf 10 Cent je km begrenzen und kaufmännisch runden“ steht zweimal im Code, die Grenze 10 zudem als magische Zahl. |
Bewertung: je Ausschnitt 1 Punkt für das Prinzip und 1 Punkt für eine passende Begründung. Für A ist auch YAGNI vertretbar, wenn die Begründung auf den unnötigen Zwischenspeicher zielt; für D auch KISS, wenn die doppelte Methode als überflüssig begründet wird.
b) Baue die Ausschnitte C und D so um, dass sie dem jeweiligen Prinzip entsprechen. Das Verhalten soll gleich bleiben. (4 Punkte)
// Ausschnitte C und D nach dem Umbau (übrige Klasse unverändert)
public class Fahrtenverwaltung {
private static final int HOECHSTSATZ_CENT_JE_KM = 10;
// Ausschnitt C
public boolean darfMitfahren(int alter, boolean einwilligungLiegtVor) {
return alter >= 18 || einwilligungLiegtVor;
}
// Ausschnitt D
public int beitragCent(int streckeMeter, int wunschsatzCentJeKm) {
int satz = Math.min(wunschsatzCentJeKm, HOECHSTSATZ_CENT_JE_KM);
return (streckeMeter * satz + 500) / 1000;
}
}Für Hin- und Rückfahrt gilt dieselbe Regel, deshalb genügt eine Methode beitragCent; die Aufrufer der beiden alten Methoden rufen sie auf. Die Grenze steht als Konstante mit Einheit im Namen.
Bewertung: C vereinfacht 2 Punkte (1 Punkt, wenn noch ein Bedingungsoperator übrig ist); D zu einer Methode zusammengeführt 1 Punkt, Konstante statt magischer Zahl 1 Punkt.
c) Nenne für zwei der vier Prinzipien die englische Langform und die Person oder das Buch, auf die das Prinzip zurückgeht. (2 Punkte)
KISS: Keep it simple, stupid, zugeschrieben dem Flugzeugingenieur Kelly Johnson (Lockheed, um 1960).
YAGNI: You Ain’t Gonna Need It, aus dem Extreme Programming (Kent Beck, Ron Jeffries, späte 1990er).
DRY: Don’t Repeat Yourself, Andrew Hunt und David Thomas, „The Pragmatic Programmer“ (1999).
Beware of Premature Optimization: nach Donald Knuth, „Structured Programming with go to Statements“ (1974).
Bewertung: je Prinzip mit Langform und Herkunft 1 Punkt, höchstens 2 Punkte.
Aufgabe 1.2: Gleiches Wissen oder zufällige Ähnlichkeit? (Basiskompetenz)
Jonas Wolff aus dem Projektteam des Mitfahrlabors hat gerade den Artikel über DRY gelesen und will alle Wiederholungen aus dem Code entfernen. Er hat dafür die folgenden Stellen gesammelt:
// Konstanten der App
class Regeln {
static final int HOECHSTSATZ_CENT_JE_KM = 10;
static final int ERINNERUNG_MINUTEN_VORHER = 10;
}
// Paar 1: Registrierung und Buchung
class Registrierung {
boolean brauchtEinwilligung(int alter) {
return alter < 18;
}
}
class Buchung {
boolean istZulaessig(int alter, boolean einwilligungLiegtVor) {
return alter >= 18 || einwilligungLiegtVor;
}
}
// Paar 2: Fahrtangebot und Profil
class Angebot {
int freiePlaetze;
boolean istBuchbar() {
return freiePlaetze > 0;
}
}
class Profil {
int anzahlBewertungen;
boolean hatBewertungen() {
return anzahlBewertungen > 0;
}
}a) Jonas schlägt vor, die beiden Konstanten durch eine gemeinsame Konstante ZEHN = 10 zu ersetzen, „damit die 10 nur einmal vorkommt“. Beurteile den Vorschlag. (3 Punkte)
Der Vorschlag ist nicht sinnvoll. Die beiden Konstanten haben zufällig denselben Wert, stellen aber verschiedenes Wissen dar: den Höchstsatz für den Kostenbeitrag (eine Regel der App) und den Zeitpunkt der Erinnerung vor einer Fahrt (eine Einstellung der Benachrichtigung). Sie ändern sich aus verschiedenen Gründen: Wird die Erinnerung auf 15 Minuten vorverlegt, darf sich der Höchstsatz nicht mitändern. Eine Konstante ZEHN würde beide Regeln koppeln und außerdem ihren Sinn verschweigen; sie ist eine magische Zahl mit Namen. DRY verlangt, dass jedes Wissen einmal dargestellt ist, nicht jeder Zahlenwert.
Bewertung: Urteil 1 Punkt, verschiedenes Wissen bzw. verschiedene Änderungsgründe erkannt 1 Punkt, Folge (Kopplung, Name ohne Bedeutung) 1 Punkt.
b) Entscheide für Paar 1 und für Paar 2, ob eine Wissensdopplung im Sinne von DRY vorliegt, und begründe Deine Entscheidung. Baue das Paar um, bei dem es sich lohnt. (5 Punkte)
Paar 1 ist eine echte Wissensdopplung: Beide Klassen setzen dieselbe Regel der App um („Minderjährige brauchen die Einwilligung der Erziehungsberechtigten“), und die Altersgrenze 18 steht an zwei Stellen. Ändert sich die Regel, müssen beide Stellen gleich geändert werden. Die Regel gehört an eine Stelle:
/** Regel der App: Minderjährige brauchen die Einwilligung der Erziehungsberechtigten. */
class Altersregel {
static final int VOLLJAEHRIG_AB_JAHREN = 18;
static boolean istVolljaehrig(int alter) {
return alter >= VOLLJAEHRIG_AB_JAHREN;
}
}
class Registrierung {
boolean brauchtEinwilligung(int alter) {
return !Altersregel.istVolljaehrig(alter);
}
}
class Buchung {
boolean istZulaessig(int alter, boolean einwilligungLiegtVor) {
return Altersregel.istVolljaehrig(alter) || einwilligungLiegtVor;
}
}Paar 2 sieht gleich aus (> 0), ist aber keine Wissensdopplung: „Ein Angebot ist buchbar, solange Plätze frei sind“ und „ein Profil hat Bewertungen“ sind zwei unabhängige Aussagen, die sich aus verschiedenen Gründen ändern können (etwa wenn ein Platz für Gepäck freigehalten werden soll). Hier bleibt der Code getrennt.
Bewertung: Paar 1 richtig entschieden und begründet 2 Punkte, Umbau 1 Punkt, Paar 2 richtig entschieden und begründet 2 Punkte.
c) Oksana Melnyk formatiert die Abfahrtszeit bisher in der Erinnerung und in der Buchungsbestätigung mit fast gleichem Code. Jetzt soll die Anzeige auf den Flurmonitoren dazukommen. Erkläre mit der Rule of Three, ob sie die Formatierung jetzt zusammenführen sollte. (2 Punkte)
Nach der Rule of Three (Don Roberts, bekannt durch Martin Fowler) duldet man eine Wiederholung beim zweiten Vorkommen und baut beim dritten um. Mit den Flurmonitoren kommt das dritte Vorkommen hinzu; jetzt ist erkennbar, was die drei Stellen gemeinsam brauchen, und Oksana sollte die Formatierung in eine Methode zusammenführen, sofern alle drei dasselbe Format zeigen sollen.
Bewertung: Regel erklärt 1 Punkt, Anwendung auf den Fall 1 Punkt.
Aufgabe 2.1: Ballast im Bestellabschluss entfernen (Kernkompetenz)
Im Onlineshop der CSB-Rad GmbH schließt die Klasse Shop Bestellungen ab: Sie berechnet Warenwert, Rabatt für den Gutscheincode KURIER10, Versandkosten (Paket, Spedition, Abholung) und Mehrwertsteuer, vergibt eine Bestellnummer ab CSB-1001, legt eine Bestätigungsmail in den Postausgang und speichert die Bestellung. Beträge sind Nettopreise in Cent, Gewichte in Gramm. Namen, Konstanten und Kommentare sind schon aufgeräumt (Stand 02 der Code-Kata dieser Artikelreihe), aber der Code enthält noch Ballast. Du findest ihn samt Charakterisierungstests im Ordner src/kata/java/stand-02 bzw. src/kata/python/stand-02 des Repositorys zur Reihe; die Tests liegen dort im Ordner tests-charakterisierung.
// vollständiger Stand aus src/kata/java/stand-02: Position.java
package de.csbrad.shop;
/** Eine Bestellposition; Preise sind Nettopreise in Cent, Gewichte in Gramm. */
public record Position(String artikelnr, String bezeichnung, int nettopreisCent, int menge, int gewichtGramm, boolean istRad) {
}
// vollständiger Stand aus src/kata/java/stand-02: Shop.java
package de.csbrad.shop;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Shop {
private static final int ERSTE_BESTELLNUMMER = 1001;
private static final String GUTSCHEINCODE_KURIER = "KURIER10";
private static final int GUTSCHEIN_RABATT_PROZENT = 10;
private static final int PAKETPAUSCHALE_CENT = 690;
private static final int HOECHSTGEWICHT_PAKET_GRAMM = 31500;
private static final int VERSANDKOSTENFREI_AB_CENT = 10000;
private static final int SPEDITION_JE_RAD_CENT = 4900;
private static final int MEHRWERTSTEUER_PROZENT = 19;
private int naechsteBestellnummer = ERSTE_BESTELLNUMMER;
private final List<Object[]> bestellungen = new ArrayList<>();
private final List<String> postausgang = new ArrayList<>();
private final Map<Integer, Integer> mehrwertsteuerCache = new HashMap<>();
public List<Object[]> getBestellungen() {
return bestellungen;
}
public List<String> getPostausgang() {
return postausgang;
}
private int vergibBestellnummer() {
int bestellnummer = naechsteBestellnummer;
naechsteBestellnummer = naechsteBestellnummer + 1;
return bestellnummer;
}
/**
* Berechnet die Beträge einer Bestellung, verschickt die Bestätigungsmail und speichert die Bestellung.
*
* @return {Warenwert, Rabatt, Versandkosten, Mehrwertsteuer, Gesamtbetrag} in Cent
*/
public int[] abschliessen(List<Position> positionen, String versandart, String gutscheincode, String kundenEmail, boolean express) {
int warenwert = 0;
for (Position position : positionen) {
if (position.menge() <= 0) {
throw new IllegalArgumentException("Menge muss positiv sein: " + position.artikelnr());
}
warenwert = warenwert + position.nettopreisCent() * position.menge();
}
int rabatt = 0;
if (gutscheincode != null && gutscheincode.equals(GUTSCHEINCODE_KURIER)) {
int warenwertFuerRabatt = 0;
for (Position position : positionen) {
warenwertFuerRabatt = warenwertFuerRabatt + position.nettopreisCent() * position.menge();
}
// kaufmännisch gerundet
rabatt = (warenwertFuerRabatt * GUTSCHEIN_RABATT_PROZENT + 50) / 100;
}
int versandkosten = 0;
String versandbezeichnung;
if (versandart.equals("PAKET")) {
// Räder passen in kein Paket; die Freigrenze gilt für den Warenwert nach Rabatt
int gesamtgewicht = 0;
for (Position position : positionen) {
if (position.istRad()) {
throw new IllegalArgumentException("Räder können nicht als Paket versandt werden");
}
gesamtgewicht = gesamtgewicht + position.gewichtGramm() * position.menge();
}
int anzahlPakete = gesamtgewicht / HOECHSTGEWICHT_PAKET_GRAMM;
if (gesamtgewicht % HOECHSTGEWICHT_PAKET_GRAMM > 0 || anzahlPakete == 0) {
anzahlPakete = anzahlPakete + 1;
}
if (warenwert - rabatt >= VERSANDKOSTENFREI_AB_CENT) {
versandkosten = 0;
} else {
versandkosten = anzahlPakete * PAKETPAUSCHALE_CENT;
}
versandbezeichnung = "Paket";
} else if (versandart.equals("SPEDITION")) {
int anzahlRaeder = 0;
for (Position position : positionen) {
if (position.istRad()) {
anzahlRaeder = anzahlRaeder + position.menge();
}
}
// ohne Rad wird eine Speditionspauschale berechnet
if (anzahlRaeder == 0) {
anzahlRaeder = 1;
}
versandkosten = anzahlRaeder * SPEDITION_JE_RAD_CENT;
versandbezeichnung = "Spedition";
} else if (versandart.equals("ABHOLUNG")) {
versandkosten = 0;
versandbezeichnung = "Abholung";
} else {
throw new IllegalArgumentException("Unbekannte Versandart: " + versandart);
}
if (warenwert == 0) {
throw new IllegalArgumentException("Bestellung ohne Warenwert");
}
// Preise sind Nettopreise; die Mehrwertsteuer kommt auf Waren und Versand
int nettobetrag = warenwert - rabatt + versandkosten;
int mehrwertsteuer;
if (mehrwertsteuerCache.containsKey(nettobetrag)) {
mehrwertsteuer = mehrwertsteuerCache.get(nettobetrag);
} else {
mehrwertsteuer = (nettobetrag * MEHRWERTSTEUER_PROZENT + 50) / 100;
mehrwertsteuerCache.put(nettobetrag, mehrwertsteuer);
}
int gesamtbetrag = nettobetrag + mehrwertsteuer;
int bestellnummer = vergibBestellnummer();
String mailtext;
if (versandart.equals("ABHOLUNG")) {
mailtext = "An: " + kundenEmail + "\nBestellung CSB-" + bestellnummer + "\nGesamtbetrag: " + (gesamtbetrag / 100) + ","
+ String.format("%02d", gesamtbetrag % 100) + " EUR";
mailtext = mailtext + "\nAbholung in der Werkstatt Bielefeld-Sennestadt";
} else {
mailtext = "An: " + kundenEmail + "\nBestellung CSB-" + bestellnummer + "\nGesamtbetrag: " + (gesamtbetrag / 100) + ","
+ String.format("%02d", gesamtbetrag % 100) + " EUR";
mailtext = mailtext + "\nVersand: " + versandbezeichnung;
}
postausgang.add(mailtext);
bestellungen.add(new Object[] {"CSB-" + bestellnummer, kundenEmail, positionen, gesamtbetrag});
return new int[] {warenwert, rabatt, versandkosten, mehrwertsteuer, gesamtbetrag};
}
}a) Nenne vier Stellen im Code, die gegen DRY, YAGNI oder Beware of Premature Optimization verstoßen, und gib jeweils das Prinzip an. (4 Punkte)
DRY: Die Schleife für
warenwertFuerRabattsummiert den Warenwert ein zweites Mal, obwohlwarenwertschon berechnet ist.DRY: Der Mailkopf (Empfänger, Bestellnummer, Gesamtbetrag) wird in beiden Zweigen der Fallunterscheidung wortgleich gebaut.
Beware of Premature Optimization: Der
mehrwertsteuerCachesoll eine einfache Rechnung beschleunigen, ist nicht gemessen und wächst unbegrenzt.YAGNI: Der Parameter
expresswird nie gelesen.
Bewertung: je Stelle mit passendem Prinzip 1 Punkt. Auch die gleiche Rundungsformel (... * prozent + 50) / 100 bei Rabatt und Mehrwertsteuer (DRY) ist als Stelle zulässig.
b) Baue die Klasse Shop so um, dass die vier Stellen aus a) beseitigt sind. Das Verhalten muss gleich bleiben: Passe die Charakterisierungstests nur dort an, wo sich der Aufruf ändert, und führe sie danach aus. Die lange Methode abschliessen darf lang bleiben. (8 Punkte)
Die Beispiellösung entspricht Stand 03 der Kata (src/kata/java/stand-03 bzw. src/kata/python/stand-03):
// vollständiger Stand aus src/kata/java/stand-03: Shop.java (Position.java unverändert)
package de.csbrad.shop;
import java.util.ArrayList;
import java.util.List;
public class Shop {
private static final int ERSTE_BESTELLNUMMER = 1001;
private static final String GUTSCHEINCODE_KURIER = "KURIER10";
private static final int GUTSCHEIN_RABATT_PROZENT = 10;
private static final int PAKETPAUSCHALE_CENT = 690;
private static final int HOECHSTGEWICHT_PAKET_GRAMM = 31500;
private static final int VERSANDKOSTENFREI_AB_CENT = 10000;
private static final int SPEDITION_JE_RAD_CENT = 4900;
private static final int MEHRWERTSTEUER_PROZENT = 19;
private int naechsteBestellnummer = ERSTE_BESTELLNUMMER;
private final List<Object[]> bestellungen = new ArrayList<>();
private final List<String> postausgang = new ArrayList<>();
public List<Object[]> getBestellungen() {
return bestellungen;
}
public List<String> getPostausgang() {
return postausgang;
}
private int vergibBestellnummer() {
int bestellnummer = naechsteBestellnummer;
naechsteBestellnummer = naechsteBestellnummer + 1;
return bestellnummer;
}
/**
* Berechnet die Beträge einer Bestellung, verschickt die Bestätigungsmail und speichert die Bestellung.
*
* @return {Warenwert, Rabatt, Versandkosten, Mehrwertsteuer, Gesamtbetrag} in Cent
*/
public int[] abschliessen(List<Position> positionen, String versandart, String gutscheincode, String kundenEmail) {
int warenwert = 0;
for (Position position : positionen) {
if (position.menge() <= 0) {
throw new IllegalArgumentException("Menge muss positiv sein: " + position.artikelnr());
}
warenwert = warenwert + position.nettopreisCent() * position.menge();
}
int rabatt = 0;
if (gutscheincode != null && gutscheincode.equals(GUTSCHEINCODE_KURIER)) {
// kaufmännisch gerundet
rabatt = (warenwert * GUTSCHEIN_RABATT_PROZENT + 50) / 100;
}
int versandkosten = 0;
String versandbezeichnung;
if (versandart.equals("PAKET")) {
// Räder passen in kein Paket; die Freigrenze gilt für den Warenwert nach Rabatt
int gesamtgewicht = 0;
for (Position position : positionen) {
if (position.istRad()) {
throw new IllegalArgumentException("Räder können nicht als Paket versandt werden");
}
gesamtgewicht = gesamtgewicht + position.gewichtGramm() * position.menge();
}
int anzahlPakete = gesamtgewicht / HOECHSTGEWICHT_PAKET_GRAMM;
if (gesamtgewicht % HOECHSTGEWICHT_PAKET_GRAMM > 0 || anzahlPakete == 0) {
anzahlPakete = anzahlPakete + 1;
}
if (warenwert - rabatt >= VERSANDKOSTENFREI_AB_CENT) {
versandkosten = 0;
} else {
versandkosten = anzahlPakete * PAKETPAUSCHALE_CENT;
}
versandbezeichnung = "Paket";
} else if (versandart.equals("SPEDITION")) {
int anzahlRaeder = 0;
for (Position position : positionen) {
if (position.istRad()) {
anzahlRaeder = anzahlRaeder + position.menge();
}
}
// ohne Rad wird eine Speditionspauschale berechnet
if (anzahlRaeder == 0) {
anzahlRaeder = 1;
}
versandkosten = anzahlRaeder * SPEDITION_JE_RAD_CENT;
versandbezeichnung = "Spedition";
} else if (versandart.equals("ABHOLUNG")) {
versandkosten = 0;
versandbezeichnung = "Abholung";
} else {
throw new IllegalArgumentException("Unbekannte Versandart: " + versandart);
}
if (warenwert == 0) {
throw new IllegalArgumentException("Bestellung ohne Warenwert");
}
// Preise sind Nettopreise; die Mehrwertsteuer kommt auf Waren und Versand
int nettobetrag = warenwert - rabatt + versandkosten;
int mehrwertsteuer = (nettobetrag * MEHRWERTSTEUER_PROZENT + 50) / 100;
int gesamtbetrag = nettobetrag + mehrwertsteuer;
int bestellnummer = vergibBestellnummer();
String mailtext = "An: " + kundenEmail + "\nBestellung CSB-" + bestellnummer + "\nGesamtbetrag: " + (gesamtbetrag / 100) + ","
+ String.format("%02d", gesamtbetrag % 100) + " EUR";
if (versandart.equals("ABHOLUNG")) {
mailtext = mailtext + "\nAbholung in der Werkstatt Bielefeld-Sennestadt";
} else {
mailtext = mailtext + "\nVersand: " + versandbezeichnung;
}
postausgang.add(mailtext);
bestellungen.add(new Object[] {"CSB-" + bestellnummer, kundenEmail, positionen, gesamtbetrag});
return new int[] {warenwert, rabatt, versandkosten, mehrwertsteuer, gesamtbetrag};
}
}In allen 16 Charakterisierungstests entfällt nur das letzte Argument (false), die erwarteten Ergebnisse bleiben gleich, zum Beispiel:
// Auszug aus src/kata/java/stand-03: tests-charakterisierung/.../ShopCharakterisierungTest.java
assertArrayEquals(new int[] {7563, 0, 690, 1568, 9821}, shop.abschliessen(List.of(LICHTSET), "PAKET", null, MAIL));Ausführen: Java mvn -q test (nachdem der Ordner tests-charakterisierung/de nach src/test/java/ kopiert wurde), Python python -m pytest tests-charakterisierung. Alle 16 Tests sind grün.
Bewertung: zweite Warenwert-Schleife entfernt und Rabatt aus warenwert berechnet 2 Punkte, Mailkopf einmal gebaut 2 Punkte, Zwischenspeicher samt Feld (und Imports) entfernt 2 Punkte, Parameter entfernt und Tests angepasst 1 Punkt, Tests ausgeführt und grün 1 Punkt.
c) Ein Teammitglied wendet ein, der Zwischenspeicher mache den Shop doch schneller. Begründe, warum Du ihn trotzdem entfernst, und beschreibe, wie Du vorgehen würdest, um die Frage sachlich zu klären. (4 Punkte)
Die gesparte Rechnung besteht aus einer Multiplikation, einer Addition und einer Division. Der Zwischenspeicher braucht dagegen einen Hashwert, einen Tabellenzugriff und (in Java) das Verpacken in Integer-Objekte; da gleiche Nettobeträge selten sind, wird fast immer gerechnet und gespeichert. Er wächst mit jeder Bestellung und wird nie geleert (schleichendes Speicherleck) und gibt der Klasse einen zusätzlichen Zustand. Ein Nutzen ist nicht belegt.
Vorgehen: zuerst ein Leistungsziel festlegen (z. B. Antwortzeit des Bestellabschlusses), dann unter realistischen Bedingungen messen. Wird das Ziel verfehlt, mit einem Profiler (cProfile, JDK Flight Recorder) den Engpass suchen; erst wenn er in der Mehrwertsteuer läge, gezielt optimieren und erneut messen. Eine einfache Vergleichsmessung (mit und ohne Zwischenspeicher, z. B. mit timeit oder JMH) zeigt außerdem, dass die direkte Rechnung schneller ist.
Bewertung: Begründung (kein Nutzen, Kosten des Zwischenspeichers, Speicherwachstum oder Zustand) 2 Punkte, Vorgehen mit Ziel, Messung und Profiler 2 Punkte.
d) Erläutere, warum das Entfernen des Parameters express riskanter ist als die anderen Umbauten und was Du vorher prüfst. (2 Punkte)
Die anderen Umbauten ändern nur das Innere der Methode. Das Entfernen des Parameters ändert die öffentliche Schnittstelle: Jeder Aufrufer muss angepasst werden, sonst übersetzt der Java-Code nicht mehr bzw. Python wirft zur Laufzeit einen TypeError. Vorher suchst Du alle Aufrufer (IDE „Find Usages“, Suche im Repository); nutzen andere Teams die Methode, markierst Du sie zuerst als veraltet und entfernst den Parameter erst in einer späteren Version.
Bewertung: Schnittstellenänderung mit Folge für die Aufrufer 1 Punkt, Prüfung der Aufrufer bzw. Übergang über „veraltet“ 1 Punkt.
Aufgabe 2.2: Den Kostenbeitrag nur einmal berechnen (Kernkompetenz)
In der Mitfahr-App Mitfahrlabor erhalten Mitfahrende eine Nachricht, sobald ihre Anfrage bestätigt ist. Die Nachricht nennt den Kostenbeitrag, den sie an die Fahrerin oder den Fahrer zahlen. Nach den Regeln der App sind höchstens 0,10 € je km und Person erlaubt; ein höherer Wunschsatz wird auf diesen Höchstsatz begrenzt. Strecken speichert die App in Metern, Beträge in Cent.
public class Fahrtangebot {
private final String haltepunkt;
private final int streckeMeter;
private final int wunschsatzCentJeKm;
public Fahrtangebot(String haltepunkt, int streckeMeter, int wunschsatzCentJeKm) {
this.haltepunkt = haltepunkt;
this.streckeMeter = streckeMeter;
this.wunschsatzCentJeKm = wunschsatzCentJeKm;
}
public String getHaltepunkt() {
return haltepunkt;
}
public int getStreckeMeter() {
return streckeMeter;
}
public int getWunschsatzCentJeKm() {
return wunschsatzCentJeKm;
}
/** Kostenbeitrag einer mitfahrenden Person in Cent, kaufmännisch gerundet. */
public int kostenbeitragCent(boolean mitRueckfahrt) {
int satz = wunschsatzCentJeKm;
if (satz > 10) {
satz = 10;
}
return (streckeMeter * satz + 500) / 1000;
}
}
public class Buchungsnachricht {
/** Text, den eine Person erhält, wenn ihre Mitfahranfrage bestätigt wurde. */
public String erstelle(Fahrtangebot angebot) {
int beitrag = (angebot.getStreckeMeter() * angebot.getWunschsatzCentJeKm() + 500) / 1000;
return "Deine Mitfahrt ab " + angebot.getHaltepunkt() + " ist bestätigt. Kostenbeitrag: "
+ (beitrag / 100) + "," + String.format("%02d", beitrag % 100) + " EUR";
}
}a) Niklas Fischer bietet eine Fahrt ab dem Busbahnhof Bad Salzuflen an (19,4 km) und gibt als Wunschsatz 13 Cent je km an. Berechne, welchen Kostenbeitrag Fahrtangebot und welchen Buchungsnachricht für eine mitfahrende Person ergibt, und erkläre, wie die Abweichung mit DRY zusammenhängt. (4 Punkte)
Fahrtangebot: Der Wunschsatz wird auf 10 Cent begrenzt: (19 400 · 10 + 500) / 1000 = 194 500 / 1000 = 194 Cent, also 1,94 €.Buchungsnachricht: Sie rechnet mit dem ungebremsten Wunschsatz: (19 400 · 13 + 500) / 1000 = 252 700 / 1000 = 252 Cent, also 2,52 € (ganzzahlige Division).
Die Regel „Kostenbeitrag = Strecke mal Satz, Satz höchstens 10 Cent je km“ ist an zwei Stellen dargestellt. Beim Kopieren ist die Begrenzung in der Nachricht verloren gegangen. Genau davor schützt DRY: Gäbe es die Regel nur einmal, könnten die beiden Stellen nicht auseinanderlaufen.
Bewertung: je Rechnung mit Ergebnis 1 Punkt, Zusammenhang mit DRY (zweimal dargestelltes Wissen, auseinandergelaufen) 2 Punkte.
b) Baue die beiden Klassen so um, dass die Regel für den Kostenbeitrag nur noch an einer Stelle steht. Entferne dabei auch, was YAGNI widerspricht, und ersetze die magische Zahl. (6 Punkte)
public class Fahrtangebot {
/** Höchstsatz laut Regeln der App: 0,10 € je km und Person. */
private static final int HOECHSTSATZ_CENT_JE_KM = 10;
private final String haltepunkt;
private final int streckeMeter;
private final int wunschsatzCentJeKm;
public Fahrtangebot(String haltepunkt, int streckeMeter, int wunschsatzCentJeKm) {
this.haltepunkt = haltepunkt;
this.streckeMeter = streckeMeter;
this.wunschsatzCentJeKm = wunschsatzCentJeKm;
}
public String getHaltepunkt() {
return haltepunkt;
}
/** Kostenbeitrag einer mitfahrenden Person in Cent, kaufmännisch gerundet. */
public int kostenbeitragCent() {
int satz = Math.min(wunschsatzCentJeKm, HOECHSTSATZ_CENT_JE_KM);
return (streckeMeter * satz + 500) / 1000;
}
}
public class Buchungsnachricht {
/** Text, den eine Person erhält, wenn ihre Mitfahranfrage bestätigt wurde. */
public String erstelle(Fahrtangebot angebot) {
int beitrag = angebot.kostenbeitragCent();
return "Deine Mitfahrt ab " + angebot.getHaltepunkt() + " ist bestätigt. Kostenbeitrag: "
+ (beitrag / 100) + "," + String.format("%02d", beitrag % 100) + " EUR";
}
}Die Nachricht fragt das Angebot nach seinem Kostenbeitrag, statt selbst zu rechnen. Der nie gelesene Parameter mitRueckfahrt ist entfernt, der Höchstsatz steht als Konstante mit Einheit im Namen, und Math.min bzw. min ersetzt das if. Die Getter für Strecke und Wunschsatz braucht in Java niemand mehr außerhalb der Klasse; sie sind deshalb ebenfalls entfallen.
Bewertung: Nachricht nutzt die Berechnung des Angebots 3 Punkte, Parameter entfernt 1 Punkt, Konstante 1 Punkt, Verhalten des Angebots unverändert 1 Punkt.
c) Schreibe zwei Unit-Tests, die sicherstellen, dass Angebot und Nachricht denselben, begrenzten Kostenbeitrag nennen und ein Wunschsatz unter dem Höchstsatz unverändert bleibt. (3 Punkte)
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class BuchungsnachrichtTest {
@Test
void nachrichtNenntBegrenztenKostenbeitrag() {
Fahrtangebot angebot = new Fahrtangebot("Busbahnhof Bad Salzuflen", 19400, 13);
String text = new Buchungsnachricht().erstelle(angebot);
assertEquals(194, angebot.kostenbeitragCent());
assertEquals("Deine Mitfahrt ab Busbahnhof Bad Salzuflen ist bestätigt. Kostenbeitrag: 1,94 EUR", text);
}
@Test
void satzUnterHoechstsatzBleibtUnveraendert() {
Fahrtangebot angebot = new Fahrtangebot("Hauptbahnhof, ZOB Steig 3 Gütersloh", 18600, 8);
assertEquals(149, angebot.kostenbeitragCent());
}
}Der erste Test wäre mit dem alten Code rot geworden: Die Nachricht hätte 2,52 EUR genannt. Er hält den Fehler als Regressionstest dauerhaft fern. Für den zweiten Fall: Ben Richters Strecke ab Gütersloh (18,6 km) mit 8 Cent je km ergibt (18 600 · 8 + 500) / 1000 = 149 Cent.
Bewertung: je Testfall mit richtigem erwarteten Wert 1 Punkt, Aufbau (Arrange – Act – Assert, sprechende Testnamen) 1 Punkt.
d) Oksana Melnyk schlägt vor, statt einer Methode in Fahrtangebot eine allgemeine Hilfsmethode Rechenhilfe.anteil(wert, faktor, teiler, obergrenze, runden) zu bauen, die auch für Rabatte und Statistiken genutzt werden kann. Beurteile den Vorschlag mit Blick auf KISS, YAGNI und DRY. (3 Punkte)
Der Vorschlag ist nicht zu empfehlen. Es gibt heute genau eine Verwendung; Rabatte und Statistiken, die dieselbe Rechnung brauchen, sind nur vermutet (YAGNI). Die Methode mit fünf Parametern und einem Schalter runden ist schwerer zu lesen als kostenbeitragCent(), und an jedem Aufruf muss man nachdenken, welcher Wert wohin gehört (KISS). DRY verlangt, dass die Regel für den Kostenbeitrag einmal dargestellt ist; das leistet die Methode im Angebot bereits. Eine allgemeine Rechenhilfe würde fachlich verschiedenes Wissen zusammenzwingen und droht zur falschen Abstraktion zu werden. Zeigen sich später drei gleichartige Rechnungen, kann man immer noch zusammenführen (Rule of Three).
Bewertung: Urteil mit Bezug auf YAGNI 1 Punkt, KISS 1 Punkt, DRY richtig ausgelegt (Wissen, nicht Rechenschema) 1 Punkt.
Aufgabe 4.1: Gemeinsame Bibliothek für App und Flurmonitore? (Expertise)
Die Flurmonitore des Mitfahrlabors zeigen offene Angebote und Gesuche anonym an: Ort, Treffpunkt, Richtung, Zeit und freie Plätze. Die Anzeigesoftware auf den Mini-Playern betreut die Schul-IT OWL mit Svenja Krämer; die App selbst entwickelt das Projektteam der Schule. Beide Programme formatieren Abfahrtszeiten, kürzen lange Haltepunktnamen und prüfen, ob ein Angebot noch freie Plätze hat. Jonas Wolff schlägt vor, diese Funktionen in eine gemeinsame Bibliothek mitfahrlabor-common auszulagern, die beide Teams einbinden müssen: „Das ist DRY, so gibt es jede Funktion nur einmal.“
a) Nimm Stellung zu Jonas’ Vorschlag. Wäge dabei DRY gegen die Kopplung der beiden Teams ab und unterscheide, welche der drei Funktionen gemeinsames fachliches Wissen darstellen. (6 Punkte)
Eine gemeinsame Bibliothek koppelt zwei Teams mit unterschiedlichen Zuständigkeiten, Release-Zyklen und Umgebungen (App-Server und Mini-Player im eigenen VLAN). Jede Änderung an der Bibliothek muss abgestimmt, versioniert und in beiden Programmen eingespielt werden; ein Fehler darin trifft beide. Dem steht der Nutzen von DRY gegenüber, der nur dort groß ist, wo sich das Wissen für beide immer gleich ändert.
Freie Plätze prüfen: Das ist fachliches Wissen der App („buchbar, solange Plätze frei sind“). Ändert sich die Regel, müssen App und Monitore sie gleich anwenden. Dieses Wissen sollte nur an einer Stelle liegen, am besten in der App bzw. der Datenbank: Die Monitore lesen ohnehin die anonyme Sicht
v_monitor, die das Ergebnis bereits liefern kann.Abfahrtszeit formatieren: Das ist Darstellung. Ob die Monitore „07:00“ und die App „Mo., 07:00 Uhr“ zeigen, dürfen beide Teams unabhängig entscheiden; die Ähnlichkeit ist zufällig.
Haltepunktnamen kürzen: ebenfalls Darstellung, abhängig von der Bildschirmgröße der Monitore. Eine gemeinsame Funktion würde einen Parameter für jede Anzeige brauchen (falsche Abstraktion).
Fazit: Keine gemeinsame Bibliothek, sondern das fachliche Wissen (freie Plätze) an einer Stelle bereitstellen und die Darstellung in jedem Team getrennt lassen. Eine kleine Wiederholung in der Formatierung ist das kleinere Übel als die dauerhafte Abstimmung zweier Teams.
Bewertung: Kopplung mit Folgen (Abstimmung, Versionen, gemeinsame Fehler) 2 Punkte, fachliches Wissen von zufälliger Ähnlichkeit unterschieden und den drei Funktionen zugeordnet 3 Punkte, begründetes Fazit 1 Punkt. Eine andere Entscheidung ist zulässig, wenn sie die Abwägung nachvollziehbar begründet.
b) Beschreibe, unter welchen Bedingungen eine gemeinsame Bibliothek doch die bessere Lösung wäre. (3 Punkte)
Eine gemeinsame Bibliothek lohnt sich, wenn sie Wissen enthält, das sich für alle Programme, die sie einbinden, immer gleich ändern muss (etwa die Regel für den Kostenbeitrag, wenn auch die Monitore Beträge zeigen würden), wenn es einen klaren Verantwortlichen gibt, der sie pflegt und versioniert, und wenn die Schnittstelle klein und stabil ist, sodass Änderungen selten sind. Hilfreich ist außerdem, wenn beide Programme in derselben Sprache und Umgebung laufen und viele Stellen (Rule of Three) dieselbe Funktion brauchen.
Bewertung: je passender Bedingung 1 Punkt, höchstens 3 Punkte.
Links und weitere Informationen
Einfach bleiben: KISS, YAGNI, DRY und Beware of Premature Optimization
Refactoring (nächster Artikel der Reihe)
Hinweis zur Nachnutzung als Open Educational Resource (OER)
Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Übungsaufgaben zu einfachem Code: KISS, YAGNI, DRY und Beware of Premature Optimization” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/einfacher-code-uebungsaufgaben veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/design-prinzipien/clean-code. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 09.11.2026.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]
