====== LU01.L09 - Vom Skript zur Funktion ======
===== 1. Analyse der Ausgangslage =====
Alle drei ''def''-Blöcke in ''ausgangslage.py'' sind **Prozeduren**:
^ Block ^ Warum Prozedur? ^
| ''artikel_hinzufuegen'' | Kein ''return''. Wirkt nach aussen: verändert die globale Liste ''lager'' und die globale Zahl ''gesamtwert''. |
| ''zeige_gesamtwert'' | Kein ''return''. Wirkt nach aussen: schreibt auf die Konsole. Liest zudem globalen Zustand statt Parameter. |
| ''zeige_teuersten'' | Kein ''return''. Berechnet zwar etwas, gibt es aber nicht heraus, sondern druckt es direkt. |
Alle drei geben ''None'' zurück und lassen sich deshalb nicht als Ausdruck verwenden.
===== 2. Musterlösung =====
~~NOCACHE~~
=== main.py ===
{{gh>https://github.com/templates-python/m323-lu01-a09-lager-funktional/blob/solution/main.py}}
**Zum ''+'' in ''artikel_hinzufuegen''**
''lager + [neuer_artikel]'' erzeugt eine **neue** Liste, ''lager.append(...)'' verändert die bestehende. Genau darin liegt der Unterschied zwischen der funktionalen und der prozeduralen Variante.
Der Preis: Die aufrufende Stelle muss das Ergebnis auffangen (''lager = artikel_hinzufuegen(...)''). Wer das vergisst, verliert den neuen Artikel – dafür kann er aber nie versehentlich fremde Daten beschädigen.
===== 3. Die Tests, die den Kern treffen =====
Zwei der neun Tests prüfen genau das, worum es didaktisch geht:
=== main_test.py ===
{{gh>https://github.com/templates-python/m323-lu01-a09-lager-funktional/blob/solution/main_test.py}}
* ''test_artikel_hinzufuegen_veraendert_original_nicht'' fällt durch, sobald jemand ''append'' auf der übergebenen Liste verwendet.
* ''test_keine_ausgabe_in_berechnung'' fällt durch, sobald eine berechnende Funktion druckt oder ''None'' liefert.
Dazu kommt das Linting: Ein ''global'' in ''main.py'' löst ''W0603 global-statement'' aus und kostet Punkte.
===== 4. Antworten auf die Kontrollfragen =====
==== Frage 1: Ersetzbarkeit ====
''gesamtwert(lager)'' hängt **nur** von seinem Parameter ab und verändert nichts. Der Aufruf kann darum jederzeit im Kopf durch seine Zahl ersetzt werden – das Programm verhält sich identisch. Diese Eigenschaft heisst **referenzielle Transparenz**.
Beim alten ''artikel_hinzufuegen('Maus', 10, 24.90)'' geht das nicht: Der Aufruf hat gar keinen Rückgabewert, den man einsetzen könnte, und seine Wirkung hängt davon ab, was vorher schon im globalen ''lager'' stand. Man muss die ganze Vorgeschichte des Programms kennen, um zu wissen, was passiert.
==== Frage 2: Rückgabewert einer Prozedur ====
ergebnis = zeige_gesamtwert()
print(ergebnis) # None
Eine Prozedur ohne ''return'' liefert in Python implizit ''None''. Die Zahl war zwar auf der Konsole zu sehen, ist im Programm aber nirgends mehr greifbar – sie lässt sich nicht weiterverwenden, nicht weiterrechnen und nicht testen.
==== Frage 3: Testbarkeit ====
Automatisch testbar sind ''gesamtwert'', ''teuerster_artikel'', ''artikel_hinzufuegen'' und ''formatiere_gesamtwert'': Man ruft sie mit einer bekannten Eingabe auf und vergleicht den Rückgabewert mit dem erwarteten Wert.
def test_gesamtwert():
lager = [{'name': 'A', 'menge': 2, 'preis': 10.0}]
assert gesamtwert(lager) == 20.0
def test_teuerster_bei_leerem_lager():
assert teuerster_artikel([]) is None
Die alten Prozeduren lassen sich so nicht prüfen: Sie geben nichts zurück. Man müsste die Konsolenausgabe abfangen und zusätzlich den globalen Zustand vor jedem Test zurücksetzen – aufwendig und fehleranfällig. Das ist der praktische Grund, warum die funktionale Programmierung Berechnung und Ausgabe trennt.
----
[[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