| Nächste Überarbeitung | Vorhergehende Überarbeitung |
| modul:m323:learningunits:lu01:loesungen:spaghetticode [2026/08/18 10:10] – Neue Loesung LU01.L11 - Spaghetticode entwirren admin | modul:m323:learningunits:lu01:loesungen:spaghetticode [2026/08/18 11:11] (aktuell) – Musterloesung via gh-Import aus dem solution-Branch admin |
|---|
| | ''i = 0'', ''fertig = False'', ''total = 0'' | Sequenz | Drei Zustandsvariablen, die von Hand gepflegt werden müssen. | | | ''i = 0'', ''fertig = False'', ''total = 0'' | Sequenz | Drei Zustandsvariablen, die von Hand gepflegt werden müssen. | |
| | ''while not fertig'' | Iteration | Endlosschleife mit Abbruchflagge statt echter Laufbedingung. | | | ''while not fertig'' | Iteration | Endlosschleife mit Abbruchflagge statt echter Laufbedingung. | |
| | ''if i >= len(...)'' | Selektion | Wird nur gebraucht, weil die Schleife nicht über die Elemente läuft. | | | ''%%if i >= len(bestellungen)%%'' | Selektion | Wird nur gebraucht, weil die Schleife nicht über die Elemente läuft. | |
| | ''if b["status"] == ...'' / ''if b["menge"] <= 0'' | Selektion | Sachlich sinnvoll, aber unnötig verschachtelt. | | | ''%%if b["status"] == "storniert"%%'' und ''%%if b["menge"] <= 0%%'' | Selektion | Sachlich sinnvoll, aber unnötig verschachtelt. | |
| | ''continue'' | **keine der drei** | Imitiert einen Sprung an den Schleifenanfang – genau das, was die strukturierte Programmierung vermeiden will. | | | ''continue'' | **keine der drei** | Imitiert einen Sprung an den Schleifenanfang – genau das, was die strukturierte Programmierung vermeiden will. | |
| | ''i = i + 1'' an drei Stellen | Sequenz | Klassische Fehlerquelle: Eine vergessene Stelle erzeugt eine Endlosschleife. | | | ''i = i + 1'' an drei Stellen | Sequenz | Klassische Fehlerquelle: Eine vergessene Stelle erzeugt eine Endlosschleife. | |
| ===== 2. Musterlösung ===== | ===== 2. Musterlösung ===== |
| |
| <code python> | ~~NOCACHE~~ |
| bestellungen = [ | === main.py === |
| {"artikel": "Maus", "status": "offen", "menge": 3, "preis": 24.90}, | {{gh>https://github.com/templates-python/m323-lu01-a11-umsatz-refactoring/blob/solution/main.py}} |
| {"artikel": "Tastatur", "status": "storniert", "menge": 2, "preis": 79.50}, | |
| {"artikel": "Monitor", "status": "offen", "menge": 0, "preis": 249.00}, | |
| {"artikel": "Kabel", "status": "geliefert", "menge": 10, "preis": 9.90}, | |
| {"artikel": "Dock", "status": "offen", "menge": 1, "preis": 189.00}, | |
| ] | |
| |
| | Übrig bleiben genau die drei Grundstrukturen: Sequenz (Initialisierung und Rückgabe), Iteration (''for''), Selektion (''if''). |
| |
| def umsatz_alt(bestellungen): | ===== 3. Was durch den Umbau verschwunden ist ===== |
| """Ausgangsfassung - bleibt zum Vergleich stehen.""" | |
| i = 0 | |
| fertig = False | |
| total = 0 | |
| while not fertig: | |
| if i >= len(bestellungen): | |
| fertig = True | |
| else: | |
| b = bestellungen[i] | |
| if b["status"] == "storniert": | |
| i = i + 1 | |
| continue | |
| else: | |
| if b["menge"] <= 0: | |
| i = i + 1 | |
| continue | |
| else: | |
| total = total + b["menge"] * b["preis"] | |
| i = i + 1 | |
| return total | |
| |
| | * **Der Index ''i''.** Damit auch jede Möglichkeit, ihn falsch zu initialisieren, an einer Stelle nicht hochzuzählen (Endlosschleife!) oder um eins danebenzuliegen. |
| | * **Die Flagge ''fertig''.** Die ''for''-Schleife kennt ihr Ende selbst. |
| | * **Die Sprünge.** Ohne ''continue'' liest sich der Ablauf von oben nach unten. |
| | * **Zwei Verschachtelungsebenen.** Die beiden Bedingungen sind zu einer benannten Frage zusammengefasst, die man laut vorlesen kann: //Wenn die Bestellung verrechenbar ist, zähle sie dazu.// |
| |
| def ist_verrechenbar(bestellung): | ===== 4. Wie geprüft wird ===== |
| """Selektion: Zaehlt diese Bestellung zum Umsatz?""" | |
| return bestellung["status"] != "storniert" and bestellung["menge"] > 0 | |
| |
| | Die Gleichwertigkeit prüft ''test_umsatz_wie_referenz'' gegen ''umsatz_alt'' aus ''referenz.py'' – über vier Datensätze, inklusive leerer Liste, komplett stornierter Liste und ungültiger Mengen. |
| |
| def umsatz(bestellungen): | Die //Struktur// lässt sich mit einem gewöhnlichen Test nicht messen: Beide Fassungen liefern 362.70. Darum lesen zwei Tests die Datei ''main.py'' als Syntaxbaum ein: |
| """Sequenz - Iteration - Selektion, je genau einmal.""" | |
| total = 0 | |
| for bestellung in bestellungen: | |
| if ist_verrechenbar(bestellung): | |
| total += bestellung["menge"] * bestellung["preis"] | |
| return total | |
| |
| | === main_test.py === |
| | {{gh>https://github.com/templates-python/m323-lu01-a11-umsatz-refactoring/blob/solution/main_test.py}} |
| |
| if __name__ == '__main__': | ''test_iteration_ohne_while'' verlangt eine ''for''-Schleife und verbietet ''while'', ''test_keine_spruenge'' verbietet ''continue'' und ''break''. Beide prüfen zusätzlich das korrekte Ergebnis – sonst wären sie mit einer leeren Datei erfüllbar. |
| print(f"Umsatz alt: {umsatz_alt(bestellungen):.2f}") # 362.70 | |
| print(f"Umsatz neu: {umsatz(bestellungen):.2f}") # 362.70 | |
| |
| assert umsatz(bestellungen) == umsatz_alt(bestellungen) | Eine Abgabe, die richtig rechnet, aber die alte Struktur behält, kommt damit auf 9 von 11 Testpunkten. |
| assert umsatz([]) == umsatz_alt([]) == 0 | |
| assert umsatz([{"status": "storniert", "menge": 5, "preis": 10.0}]) == 0 | |
| assert umsatz([{"status": "offen", "menge": -2, "preis": 10.0}]) == 0 | |
| print("Beide Fassungen liefern dasselbe Ergebnis.") | |
| </code> | |
| | |
| ===== 3. Was durch den Umbau verschwunden ist ===== | |
| | |
| * **Der Index ''i''.** Damit auch jede Möglichkeit, ihn falsch zu initialisieren, an einer Stelle nicht hochzuzählen (Endlosschleife!) oder um eins danebenzuliegen. | |
| * **Die Flagge ''fertig''.** Die ''for''-Schleife kennt ihr Ende selbst. | |
| * **Die Sprünge.** Ohne ''continue'' liest sich der Ablauf von oben nach unten. | |
| * **Zwei Verschachtelungsebenen.** Die beiden Bedingungen sind zu einer benannten Frage zusammengefasst, die man laut vorlesen kann: //„Wenn die Bestellung verrechenbar ist, zähle sie dazu."// | |
| | |
| Übrig bleiben genau die drei Grundstrukturen: Sequenz (Initialisierung und Rückgabe), Iteration (''for''), Selektion (''if''). | |
| |
| ===== 4. Antworten auf die Zusatzfragen ===== | ===== 5. Antworten auf die Zusatzfragen ===== |
| |
| ==== Frage 1: Vergessenes Hochzählen ==== | ==== Frage 1: Vergessenes Hochzählen ==== |
| ==== Frage 2: Böhm und Jacopini ==== | ==== Frage 2: Böhm und Jacopini ==== |
| |
| Der Ausgangscode brauchte scheinbar Sprünge (''continue''), um „diesen Fall zu überspringen". Die neue Fassung zeigt: Dieselbe Wirkung entsteht durch **Selektion** – statt zu überspringen, wird nur im passenden Fall etwas getan. Das ist genau die Aussage des Theorems: Jeder berechenbare Algorithmus lässt sich allein mit Sequenz, Selektion und Iteration ausdrücken. Sprünge sind bequem, aber nie zwingend nötig. | Der Ausgangscode brauchte scheinbar Sprünge (''continue''), um einen Fall zu überspringen. Die neue Fassung zeigt: Dieselbe Wirkung entsteht durch **Selektion** – statt zu überspringen, wird nur im passenden Fall etwas getan. Das ist genau die Aussage des Theorems: Jeder berechenbare Algorithmus lässt sich allein mit Sequenz, Selektion und Iteration ausdrücken. Sprünge sind bequem, aber nie zwingend nötig. |
| |
| ==== Frage 3: Deklarative Formulierung ==== | ==== Frage 3: Deklarative Formulierung ==== |
| |
| //„Der Umsatz ist die Summe aus Menge mal Preis über alle nicht stornierten Bestellungen mit positiver Menge."// | //Der Umsatz ist die Summe aus Menge mal Preis über alle nicht stornierten Bestellungen mit positiver Menge.// |
| |
| In Python direkt so ausdrückbar: | In Python direkt so ausdrückbar: |
| <code python> | <code python> |
| def umsatz_deklarativ(bestellungen): | def umsatz_deklarativ(bestellungen): |
| return sum(b["menge"] * b["preis"] for b in bestellungen if ist_verrechenbar(b)) | return sum(b['menge'] * b['preis'] for b in bestellungen if ist_verrechenbar(b)) |
| </code> | </code> |
| |