Funktionen: ein benannter Kasten mit einer Ausgabe
Argumente, Rückgabewerte, Gültigkeitsbereich und Nebenwirkungen — und warum nur ein Kasten von der Größe eines Satzes wirklich testbar ist.
Du hast dieselben drei Zeilen zweimal geschrieben, an zwei Stellen, mit einer geänderten Zahl. Dann hast du einen Fehler in der einen Kopie behoben und die andere vergessen. Eine Funktion existiert, um das zu beenden. Sie ist ein benannter Kasten: Du gibst Werte hinein, ein Wert kommt heraus, und den Inhalt schreibst du einmal.
Stell sie dir als Maschine mit Fächern vor. Die Eingaben heißen Parameter, wenn du den Kasten beschreibst, und Argumente, wenn du sie einsetzt. Die Ausgabe ist das, was aus return zurückkommt. Sonst überschreitet nichts die Wand: Der Kasten sieht vom restlichen Programm nur das, was du ihm gereicht hast.
inputs box output
price, count --> totalPrice --> a number
price = 2.50, count = 4 --> 10.00Zwei Eingaben, eine Ausgabe. Die Wand in der Mitte ist der Kern der ganzen Idee.Derselbe Kasten in drei Sprachen: Preis mal Anzahl.
def total_price(price, count):
return price * count
total = total_price(2.50, 4)Python: def, ein Doppelpunkt und der eingerückte Rumpf.function totalPrice(price, count) {
return price * count;
}
const total = totalPrice(2.50, 4);JavaScript: function, geschweifte Klammern und return mit Semikolon.func totalPrice(price float64, count int) float64 {
return price * float64(count)
}
total := totalPrice(2.50, 4)Go: func, der Typ jeder Eingabe und der Typ der Ausgabe nach der schließenden Klammer.Argumente werden den Parametern nach Position zugeordnet, nicht nach Bedeutung. Die Sprache hat keine Ahnung, dass der erste Parameter price hieß; sie weiß nur, dass das erste Argument im ersten Fach landet.
total_price(2.50, 4) arguments in the call
2.50 -> price parameters in the definition
4 -> count
total_price(4, 2.50) same shape, different meaningDieselben zwei Zahlen, vertauscht. Beide Zeilen laufen. Nur eine ist richtig.Kleine Funktionen sind leicht zu testen, und das ist der praktische Grund, sie zu schreiben. In dev.testing hast du gesehen, dass ein Test eine Behauptung darüber ist, was der Code tut. Eine Funktion mit zwei Eingaben und einer Ausgabe macht diese Behauptung mühelos aufzuschreiben, weil es sonst nichts vorzubereiten gibt: keine Datenbank, keinen Browser, keine Reihenfolge von Ereignissen.
def discount(total):
if total >= 20:
return total * 0.9
return total
assert discount(20) == 18.0
assert discount(10) == 10.0Zwei Zeilen beschreiben die ganze Funktion. Keine Vorbereitung, kein Zustand, keine Überraschungen.Die Regel ist also nicht ästhetisch. Eine Funktion mit vierzig Zeilen, die drei globale Variablen liest, lässt sich nicht testen, ohne die halbe Welt um sie herum zu bauen, also testet sie niemand, also bricht sie. Ein Kasten von der Größe eines Satzes lässt sich in einer Zeile prüfen und in einer Minute reparieren.
Der Gültigkeitsbereich beantwortet die Frage, was ein Name sehen kann. Wenn du eine Funktion aufrufst, bekommt sie ihr eigenes kleines Zimmer. Namen, die in diesem Zimmer entstehen, existieren nur, solange der Aufruf läuft, und verschwinden, wenn return die Antwort zurückgibt. Die Funktion kann Namen von außen lesen, aber das Außen kann nicht hineinsehen.
const rate = 0.21;
function vat(net) {
const inner = net * rate;
return inner;
}
vat(100)
innerrate ist innerhalb der Funktion sichtbar. inner ist außerhalb nicht sichtbar.Und schließlich ein ehrliches Wort zu Nebenwirkungen. Eine reine Funktion betrachtet nur ihre Argumente und erzeugt nur ihren Rückgabewert. Rufe sie mit denselben Argumenten tausendmal auf, und du bekommst tausendmal dieselbe Antwort. Eine unreine Funktion ändert zusätzlich etwas außerhalb ihrer selbst: Sie gibt aus, sie schreibt eine Datei, sie schickt eine Anfrage, sie erhöht einen Zähler.
pure: def vat(net): return net * 0.21
vat(100) always 21.0
impure: def vat(net):
print("calculating")
total = net * 0.21
save_to_file(total)
return totalDie erste kannst du vorhersagen. Die zweite kannst du nur laufen lassen und hoffen.Praxis
Eine Funktion soll den Preis mit Steuer liefern. Sie gibt die richtige Zahl aus, aber der aufrufende Code bekommt nichts zurück. Worin liegt der Fehler?
Hinweise
- Ein Kasten gibt einen Wert zurück; einen Wert ausgeben und einen Wert zurückgeben sind zwei verschiedene Handlungen.
- Frage, was der aufrufende Code erhält. Wenn die Funktion die Antwort auf dem Bildschirm zeigt und nichts zurückreicht, bekommt der Aufrufer nichts.
Ordne jeder Funktion zu, was sie mit der Welt außerhalb ihrer selbst macht.
Hinweise
- Eine reine Funktion ist ein Kasten, den du allein aus seinen Eingaben vorhersagen kannst.
- Nebenwirkungen sind nichts Böses: Ein Programm, das nie etwas schreibt oder ausgibt, tut nichts Nützliches. Der Punkt ist, sie zu bemerken.
Bringe die Momente eines einzigen Funktionsaufrufs in die richtige Reihenfolge, vom Aufruf bis zur Zeile danach.
- 1Der Aufruf beginnt, und die Argumente landen in den Fächern der Parameter
- 2Der Rumpf läuft und liest seine eigenen Namen
- 3return reicht einen Wert an den Aufrufer zurück
- 4Der aufrufende Code berechnet die Werte der Argumente
- 5Die Funktion endet, und der Aufrufer fährt mit der nächsten Zeile fort
Hinweise
- Der Aufruf kann seine Arbeit nicht tun, bevor die Argumente einen Wert haben.
- Namen im Kasten entstehen, wenn der Aufruf beginnt, und verschwinden, wenn return die Antwort zurückgibt.