====== LU07.L06 - Unpure wird pure ======
===== Funktionaler Kern =====
def mit_einlagerung(bestand, artikel, menge):
"""Neuer Bestand mit zusätzlicher Menge. Mutiert nichts."""
return {**bestand, artikel: bestand.get(artikel, 0) + menge}
def mit_entnahme(bestand, artikel, menge):
"""(neuer Bestand, erfolgreich?) - bei zu wenig Bestand bleibt alles gleich."""
if bestand.get(artikel, 0) < menge:
return bestand, False
return {**bestand, artikel: bestand[artikel] - menge}, True
===== Schale =====
def lauf():
bestand = {}
protokoll = []
bestand = mit_einlagerung(bestand, "maus", 5)
protokoll.append("+5 maus")
print(f"maus: {bestand['maus']}")
bestand = mit_einlagerung(bestand, "maus", 3)
protokoll.append("+3 maus")
print(f"maus: {bestand['maus']}")
bestand, ok = mit_entnahme(bestand, "maus", 2)
if ok:
protokoll.append("-2 maus")
else:
print("zu wenig Bestand")
bestand, ok = mit_entnahme(bestand, "maus", 99)
if not ok:
print("zu wenig Bestand")
return bestand, protokoll
lauf() -> ({'maus': 6}, ['+5 maus', '+3 maus', '-2 maus'])
Identisch zum Verhalten vorher.
===== Tests, die die Reinheit belegen =====
def test_kein_seiteneffekt():
original = {"maus": 5}
neu = mit_einlagerung(original, "maus", 3)
assert neu == {"maus": 8}
assert original == {"maus": 5} # Original unverändert
def test_deterministisch():
bestand = {"maus": 5}
assert mit_entnahme(bestand, "maus", 2) == mit_entnahme(bestand, "maus", 2)
def test_zu_wenig_bestand():
assert mit_entnahme({"maus": 1}, "maus", 99) == ({"maus": 1}, False)
Der Test ''test_deterministisch'' würde mit der alten Fassung scheitern: Dort liefert der zweite Aufruf einen anderen Bestand als der erste.
===== Antworten auf die Leitfragen =====
* **Zwei Rückgaben:** als Tuple ''(bestand, ok)''. Alternativ eine frozen Dataclass ''Ergebnis(bestand, erfolgreich, meldung)'', wenn mehr Information nötig wird.
* **Protokoll:** in die Schale. Es ist eine Aufzeichnung dessen, was passiert ist, nicht Teil der Berechnung. Wer es im Kern braucht, gibt es als weiteren Rückgabewert zurück, statt es zu mutieren.
* **Einfacher testbar:** Der Kern braucht keinen Startzustand und kein Aufräumen zwischen den Tests. Vorher musste ''lager'' und ''protokoll'' vor jedem Test zurückgesetzt werden - vergisst man das, hängen die Tests voneinander ab.
In Flask ist dieses Muster die Regel und nicht die Ausnahme: Die Route liest den Request und schreibt die Response (Schale), die Berechnung liegt in pure Funktionen, die ohne laufenden Server getestet werden können.
----
{{tag>M323-LU07 M323-D1I}}
[[https://creativecommons.org/licenses/by-nc-sa/4.0/ch/|{{https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png}}]] (c) Kevin Maurizi