Eine App für den Schulbus
AFB I–IIDer Schülerrat eines Landkreises beauftragt einen Informatikkurs mit einer App für die Schulbusse. Material 1 gibt das Gespräch mit dem Schülerrat wieder, das als Grundlage für das Pflichtenheft dient. Der Kurs arbeitet in drei Teams, die die Klassen getrennt implementieren sollen.
„Wir wollen in der App unsere Buslinie auswählen und für unsere Haltestelle sehen, um wie viel Uhr der Bus dort abfährt. Jede Linie hat eine Nummer, zum Beispiel 512, und startet zu einer festen Uhrzeit. Sie fährt bis zu 20 Haltestellen in fester Reihenfolge an. Für jede Haltestelle kennt der Fahrplan die Fahrzeit in Minuten ab dem Start der Linie. Die Busfahrerin meldet eine Verspätung in Minuten, die dann für alle folgenden Abfahrten der Linie gilt. Bei jeder Haltestelle soll man sehen, ob sie barrierefrei ist. Wichtig ist uns, dass die App auch ohne Netz den zuletzt geladenen Fahrplan zeigt, dass die Abfahrtszeit nach höchstens zwei Klicks sichtbar ist und dass keine Standortdaten gespeichert werden.“
- Nennen Sie aus Material 1 drei funktionale Anforderungen (was die App leisten soll) und drei nicht-funktionale Anforderungen (wie sie es leisten soll).
- Erstellen Sie ein Klassendiagramm mit den Klassen
BuslinieundHaltestelleeinschließlich Beziehung und Kardinalitäten, das die funktionalen Anforderungen abdeckt. Die Abfahrtszeit an einer Haltestelle soll über den Namen der Haltestelle erfragt werden können. - Begründen Sie, warum die drei Teams das Klassendiagramm verbindlich vereinbaren sollten, bevor sie mit der Implementierung beginnen.
Hinweise
Hinweis zu Aufgabe a)
Hinweis zu Aufgabe b)
Hinweis zu Aufgabe c)
Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
Funktional (drei davon): Buslinie auswählen; Abfahrtszeit an einer gewählten Haltestelle anzeigen; Verspätung einer Linie melden und bei den Abfahrtszeiten berücksichtigen; Barrierefreiheit einer Haltestelle anzeigen.
Nicht-funktional: Verfügbarkeit ohne Netz (zuletzt geladener Fahrplan); Bedienbarkeit (Abfahrtszeit nach höchstens zwei Klicks); Datenschutz (keine Speicherung von Standortdaten). Auch die Obergrenze von 20 Haltestellen je Linie kann als Randbedingung genannt werden.
Erwartungshorizont zu Aufgabe b)
- - nummer: Zeichenkette
- - startzeit: Ganzzahl
- - verspaetung: Ganzzahl
- - haltestellen: Reihung vom Typ Haltestelle
- - anzahl: Ganzzahl
- c Buslinie(nummer: Zeichenkette, startzeit: Ganzzahl)
- + getNummer(): Zeichenkette
- + hinzufuegen(h: Haltestelle): Wahrheitswert
- + verspaetungMelden(minuten: Ganzzahl)
- + getAbfahrt(name: Zeichenkette): Ganzzahl
- + getHaltestelle(name: Zeichenkette): Haltestelle
- - name: Zeichenkette
- - fahrzeit: Ganzzahl
- - barrierefrei: Wahrheitswert
- c Haltestelle(name: Zeichenkette, fahrzeit: Ganzzahl, barrierefrei: Wahrheitswert)
- + getName(): Zeichenkette
- + getFahrzeit(): Ganzzahl
- + istBarrierefrei(): Wahrheitswert
Die Reihung wird im Konstruktor mit 20 Plätzen erzeugt; hinzufuegen liefert false, wenn sie voll ist. getAbfahrt(name) liefert startzeit + verspaetung + fahrzeit der gefundenen Haltestelle, z. B. −1, falls es keine Haltestelle dieses Namens gibt. Die Verspätung gehört zur Linie, nicht zur Haltestelle, weil sie für alle Abfahrten der Linie gilt. Da die Fahrzeit von der Linie abhängt, steht ein Haltestellen-Objekt für einen Halt dieser Linie; eine Haltestelle, die zwei Linien anfahren, erscheint also in beiden Linien als eigenes Objekt. Andere schlüssige Entwürfe (z. B. getAbfahrt(index: Ganzzahl)) sind gleichwertig, sofern sie alle funktionalen Anforderungen abdecken.
Erwartungshorizont zu Aufgabe c)
Das Klassendiagramm legt die öffentliche Schnittstelle fest: Klassennamen, Methodennamen, Parameter- und Rückgabetypen. Nur wenn diese feststehen, kann das Team „Buslinie“ Aufrufe wie h.getFahrzeit() programmieren, während das Team „Haltestelle“ die Methode noch implementiert – die Teams arbeiten parallel, ohne auf fertigen Quelltext zu warten. Ohne Vereinbarung entstehen abweichende Namen oder Typen (etwa getZeit(): Zeichenkette statt getFahrzeit(): Ganzzahl), die erst beim Zusammenfügen auffallen und dann aufwendig angepasst werden müssen. Außerdem können Testfälle schon vor der Implementierung gegen die Schnittstelle formuliert werden. Die interne Umsetzung (private Attribute) bleibt dabei jedem Team selbst überlassen.
Buchungssystem für einen Escape-Room
AFB II–IIIEin Escape-Room-Anbieter lässt ein Buchungssystem entwickeln. Er hat mehrere Räume, zum Beispiel „Pharao“ für 2 bis 6 Personen. Jeder Raum kann täglich in fünf Zeitfenstern (0 bis 4) gebucht werden, pro Zeitfenster von genau einer Gruppe. Eine Buchung enthält den Gruppennamen und die Personenzahl, die zwischen Mindest- und Höchstzahl des Raums liegen muss. Gebuchte Zeitfenster sollen auch wieder freigegeben werden können.
Das Projektteam hat den Entwurf in Material 4 erstellt. Aylin soll die Klasse Raum implementieren, Jannik die Klasse Escaperoom, die alle Räume verwaltet, und Sofia die Bedienoberfläche. Jannik schlägt vor: „Wir fangen gleich an. Jeder programmiert seine Klasse so, wie er sie braucht, und wenn Sofias Oberfläche fertig ist, testen wir alles zusammen.“
- - name: Zeichenkette
- - minSpieler: Ganzzahl
- - maxSpieler: Ganzzahl
- - zeitfenster: Reihung vom Typ Buchung
- c Raum(name: Zeichenkette, minSpieler: Ganzzahl, maxSpieler: Ganzzahl)
- + getName(): Zeichenkette
- - gruppe: Zeichenkette
- - personen: Ganzzahl
- c Buchung(gruppe: Zeichenkette, personen: Ganzzahl)
- + getGruppe(): Zeichenkette
- + getPersonen(): Ganzzahl
Aylins erste Fassung der Methode buchen:
public boolean buchen(int slot, String gruppe, int personen) {
if (slot < 0 || slot > zeitfenster.length) {
return false;
}
if (personen < minSpieler || personen >= maxSpieler) {
return false;
}
if (zeitfenster[slot] == null) {
zeitfenster[slot] = new Buchung(gruppe, personen);
}
return true;
}
Raum erzeugt zeitfenster als Reihung mit fünf Plätzen; freie Zeitfenster enthalten null.- Entwerfen Sie die öffentlichen Methoden der Klasse
Raum, die Jannik und Sofia benötigen, als Schnittstelle: Geben Sie jede Methode in Diagrammnotation an und beschreiben Sie ihre Wirkung einschließlich des Verhaltens bei ungültigen Eingaben. - Entwickeln Sie für die Methode
bucheneine Folge von mindestens acht Testfällen für einen neu erzeugten Raum „Pharao“ (2 bis 6 Personen), die Normalfälle, Grenzfälle und ungültige Eingaben abdeckt. Geben Sie zu jedem Testfall das erwartete Ergebnis an. - Wenden Sie Ihre Testfälle auf Aylins Fassung von
buchenan, benennen Sie die aufgedeckten Fehler und geben Sie eine korrigierte Fassung an. - Diskutieren Sie Janniks Vorschlag zum Vorgehen im Projekt.
Hinweise
Hinweis zu Aufgabe a)
Hinweis zu Aufgabe b)
Hinweis zu Aufgabe c)
> und >= und darauf, wann true geliefert wird.Hinweis zu Aufgabe d)
Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
| Methode | Beschreibung |
|---|---|
+ getName(): Zeichenkette | liefert den Namen des Raums |
+ getMinSpieler(): Ganzzahl, + getMaxSpieler(): Ganzzahl | liefern die zulässige Mindest- bzw. Höchstzahl, z. B. für die Anzeige in der Oberfläche |
+ istFrei(slot: Ganzzahl): Wahrheitswert | liefert true, wenn das Zeitfenster existiert und nicht gebucht ist, sonst false |
+ buchen(slot: Ganzzahl, gruppe: Zeichenkette, personen: Ganzzahl): Wahrheitswert | legt eine Buchung an und liefert true, falls slot zwischen 0 und 4 liegt, das Zeitfenster frei ist und personen zwischen minSpieler und maxSpieler (jeweils einschließlich) liegt; sonst bleibt alles unverändert und es wird false geliefert |
+ getBuchung(slot: Ganzzahl): Buchung | liefert die Buchung im Zeitfenster; null, falls es frei ist oder nicht existiert |
+ stornieren(slot: Ganzzahl): Wahrheitswert | gibt ein gebuchtes Zeitfenster frei und liefert true; bei freiem oder ungültigem Zeitfenster false |
+ anzahlFrei(): Ganzzahl | liefert die Anzahl der freien Zeitfenster |
Erwartet werden mindestens istFrei (oder getBuchung), buchen und stornieren mit vollständigen Typen und eindeutig festgelegtem Verhalten bei ungültigen Eingaben (kein Programmabbruch). Die Reihung selbst gehört nicht zur Schnittstelle.
Erwartungshorizont zu Aufgabe b)
Ausgangszustand: Raum r = new Raum("Pharao", 2, 6); – die Tests werden in dieser Reihenfolge ausgeführt.
| Nr. | Aufruf | Art | Erwartet |
|---|---|---|---|
| T1 | r.buchen(1, "Füchse", 4) | Normalfall | true |
| T2 | r.buchen(0, "Duo", 2) | Grenzfall: erstes Fenster, Mindestzahl | true |
| T3 | r.buchen(2, "Sechser", 6) | Grenzfall: Höchstzahl | true |
| T4 | r.buchen(3, "Solo", 1) | ungültig: eine Person zu wenig | false |
| T5 | r.buchen(3, "Riesen", 7) | ungültig: eine Person zu viel | false |
| T6 | r.buchen(1, "Eulen", 3) | ungültig: Fenster 1 schon belegt | false, Buchung „Füchse“ bleibt |
| T7 | r.buchen(4, "Spät", 3) | Grenzfall: letztes Fenster | true |
| T8 | r.buchen(5, "Nacht", 3) | ungültig: Fenster existiert nicht | false, kein Abbruch |
| T9 | r.buchen(-1, "Früh", 3) | ungültig: negatives Fenster | false |
Entscheidend ist, dass jede Grenze von beiden Seiten getestet wird und das erwartete Ergebnis vor der Ausführung aus der Anforderung abgeleitet ist, nicht aus dem Programm.
Erwartungshorizont zu Aufgabe c)
T1, T2, T4, T5, T7 und T9 liefern das erwartete Ergebnis. Drei Tests schlagen fehl:
T3 liefert false statt true: personen >= maxSpieler schließt die zulässige Höchstzahl 6 aus; richtig ist personen > maxSpieler.
T6 liefert true statt false: Die Buchung „Füchse“ wird zwar nicht überschrieben, die Methode meldet aber auch bei belegtem Fenster Erfolg – die Gruppe „Eulen“ hielte sich für gebucht.
T8 bricht mit einer ArrayIndexOutOfBoundsException ab: slot > zeitfenster.length lässt slot = 5 durch, der letzte gültige Index ist aber 4; richtig ist >=.
public boolean buchen(int slot, String gruppe, int personen) {
if (slot < 0 || slot >= zeitfenster.length) {
return false;
}
if (personen < minSpieler || personen > maxSpieler) {
return false;
}
if (zeitfenster[slot] != null) {
return false;
}
zeitfenster[slot] = new Buchung(gruppe, personen);
return true;
}
Mit dieser Fassung liefern T1 bis T9 genau die erwarteten Ergebnisse.
Erwartungshorizont zu Aufgabe d)
Pro: Alle drei beginnen sofort, niemand wartet; der Entwurf in Material 4 legt die Attribute bereits fest. Ein gemeinsamer Test am Ende zeigt, ob das Gesamtsystem aus Sicht der Nutzer funktioniert.
Contra: „Jeder programmiert seine Klasse so, wie er sie braucht“ bedeutet, dass es keine vereinbarte Schnittstelle gibt. Material 4 enthält für Raum außer getName keine Methoden; Jannik und Sofia würden Methoden aufrufen, die Aylin anders benennt oder anders festlegt (etwa Rückgabe void statt Wahrheitswert). Solche Fehler zeigen sich erst beim Zusammenfügen und verursachen dann großen Nacharbeitsaufwand. Testen erst am Ende über die Oberfläche macht die Fehlersuche schwer: Ein falsches Ergebnis kann aus jeder der drei Klassen stammen. Die Fehler aus c) wären schon mit einem Test der einzelnen Klasse sofort aufgefallen. Zudem hängt der Test von Sofias Oberfläche ab – verzögert sie sich, verzögert sich jeder Test.
Ergebnis: Der Vorschlag ist in dieser Form abzulehnen. Sinnvoll ist, zuerst gemeinsam die Schnittstellen (wie in a) festzulegen und Testfälle (wie in b) aus den Anforderungen abzuleiten. Danach können die drei tatsächlich parallel arbeiten, jede Klasse wird für sich getestet, und der Gesamttest am Ende ist nur noch eine Ergänzung.
