Eigene Funktionen schreiben
Klare Grenzen für Funktionen wählen
Wenn du weißt, wie du eine Funktion definierst, stellt sich eine neue Frage: Welche Zeilen sollen zu einer Funktion werden?
Es gibt keine Formel, die immer die perfekte Antwort liefert. Es gibt aber hilfreiche Anhaltspunkte.
Beginne mit einer klaren Zuständigkeit
Schau noch einmal auf das Menü aus Kapitel 7. Seine Befehle benennen bereits getrennte Aufgaben:
| Befehl | Aufgabe | Mögliche Funktion |
|---|---|---|
add | einen Eintrag abfragen und speichern | add_item(items) |
list | die gespeicherten Einträge anzeigen | show_items(items) |
count | anzeigen, wie viele Einträge gespeichert sind | show_count(items) |
Diese Grenzen ergeben sich ganz natürlich, weil jeder Funktionsname eine vollständige Aktion beschreibt.
Vergleiche das mit:
def handle_everything():
# print the title
# run the menu loop
# add items
# list items
# count items
# say goodbye
pass
Technisch ist das eine Funktion, aber ihr Name und ihr Funktionskörper verstecken weiterhin das ganze Programm an einer Stelle. Viele Aufgaben in eine Funktion zu packen macht sie nicht leichter verständlich.
Am anderen Ende des Spektrums:
def print_number(number):
print(number)
def add_one(number):
return number + 1
Winzige Funktionen können nützlich sein. Jeden einfachen Ausdruck auszulagern kann aber dazu führen, dass man zwischen Namen hin- und herspringen muss, ohne etwas dazuzulernen.
Eine gute erste Frage lautet:
Kann ich die Aufgabe dieser Funktion mit einer kurzen Tätigkeitsbeschreibung ausdrücken?
Show the stored items erfüllt das. Handle the menu and all its commands and also print the final report deutet darauf hin, dass mehrere Aufgaben vermischt werden.
Entscheide, was die Grenze überschreitet
Sobald die Aufgabe klar ist, stelle zwei weitere Fragen:
Welche Informationen braucht die Funktion?
Braucht der aufrufende Code einen Wert zurück?
Zum Beispiel:
def count_message(items):
return f"Items stored: {len(items)}"
Die Funktion braucht die Liste, deshalb ist items ein Parameter. Der aufrufende Code braucht den fertigen Text, deshalb gibt die Funktion ihn zurück.
Diese Funktion hat eine andere Aufgabe:
def show_count(items):
print(f"Items stored: {len(items)}")
Sie braucht ebenfalls items, aber ihre Aufgabe besteht ausdrücklich darin, die Anzahl anzuzeigen. Sie gibt sie aus und muss die Nachricht nicht zurückgeben.
Beide Entwürfe können sinnvoll sein. Entscheide danach, was der aufrufende Code braucht.
Halte den Hauptablauf lesbar
Das Ziel der Umstrukturierung in Kapitel 8 ist nicht, die Gesamtzahl der Zeilen zu verringern. Die Funktionsdefinitionen können die Datei etwas länger machen.
Die Verbesserung zeigt sich in der Menüschleife:
if command == "add":
add_item(items)
elif command == "list":
show_items(items)
elif command == "count":
show_count(items)
Die Schleife liest sich jetzt wie eine Übersicht der Auswahlmöglichkeiten. Wer den Gesamtplan verstehen möchte, kann hier bleiben. Wer die Details des Auflistens wissen möchte, kann direkt zu show_items gehen.
Das ist Struktur: unterschiedliche Detailstufen, jede an einem klaren Platz.
Eine praktische Prüfliste
Bevor du eine Funktion auslagerst, frage dich:
Kann ich dem Vorgang einen präzisen Namen geben?
Hat er eine Hauptaufgabe?
Sind seine benötigten Parameter klar?
Ist klar, ob er etwas ausgeben, zurückgeben oder verändern soll?
Wird der aufrufende Code dadurch leichter lesbar?
Du wirst in diesen Entscheidungen besser, indem du sie triffst, das Ergebnis liest und überarbeitest.
Welche Funktion hat am deutlichsten eine einzelne Aufgabe?
Eine Funktion erstellt einen bereinigten Befehl, den eine if-Anweisung vergleichen wird. Was sollte die Funktion tun?