Abschnitt 6 · Lektion 5
Funktionen aufteilen
Diese Lektion bringt dir keine neue Schreibweise bei. Sie beantwortet eine Frage, die dir ab jetzt in jedem Programm begegnet: Wann ist eine Funktion zu groß, und was macht man dann mit ihr.
Drei Aufgaben in einem Block
<?php
declare(strict_types=1);
$liste = [
["name" => "Kabel", "menge" => 3, "einzel" => 990],
["name" => "Maus", "menge" => 1, "einzel" => 2490],
["name" => "Tastatur", "menge" => 2, "einzel" => 4365],
];
function baueRechnung(array $liste, string $nummer): string
{
$zeilen = [];
foreach ($liste as $posten) {
$wert = $posten["menge"] * $posten["einzel"];
$zeilen[] = $posten["name"] . ": " . $posten["menge"] . " x "
. number_format($posten["einzel"] / 100, 2, ",", ".") . " = "
. number_format($wert / 100, 2, ",", ".");
}
$summe = 0;
foreach ($liste as $posten) {
$summe += $posten["menge"] * $posten["einzel"];
}
$zeilen[] = "Rechnung " . $nummer . ": " . count($liste) . " Posten, "
. number_format($summe / 100, 2, ",", ".") . " EUR";
return implode("\n", $zeilen);
}
echo baueRechnung($liste, "2026-004"), "\n"; baueRechnung() funktioniert und ist trotzdem zu groß. Sie tut drei verschiedene Dinge: Sie rechnet
je Posten den Wert aus, sie baut daraus Text, und sie addiert eine Gesamtsumme. Wer sie liest, muss
alle drei gleichzeitig im Kopf haben.
Zwei Anzeichen, die zuverlässig sind:
Der Name braucht ein „und”. Ehrlich hieße die Funktion rechneUndFormatiereUndSummiere(). Wenn
du einen Namen nur findest, indem du ein „und” einbaust, sind es in Wahrheit mehrere Funktionen.
Du musst beim Lesen zurückblättern. In der Zeile mit $summe willst du wissen, wie der Wert
weiter oben berechnet wurde, und scrollst nach oben. Bei einer Funktion, die auf einen Bildschirm
passt, kommt das nicht vor.
Und noch etwas steckt hier drin: Der Rechenweg menge * einzel steht zweimal in derselben
Funktion. Das ist derselbe Gedanke wie in Lektion 6.1, nur eine Ebene kleiner, und es ist genau die
Stelle, an der die Aufgabe zu dieser Lektion ansetzt.
Vier kurze statt einer langen
<?php
declare(strict_types=1);
$liste = [
["name" => "Kabel", "menge" => 3, "einzel" => 990],
["name" => "Maus", "menge" => 1, "einzel" => 2490],
["name" => "Tastatur", "menge" => 2, "einzel" => 4365],
];
function postenSumme(array $posten): int
{
return $posten["menge"] * $posten["einzel"];
}
function zeileFuerPosten(array $posten): string
{
return $posten["name"] . ": " . $posten["menge"] . " x "
. number_format($posten["einzel"] / 100, 2, ",", ".") . " = "
. number_format(postenSumme($posten) / 100, 2, ",", ".");
}
function gesamtsumme(array $liste): int
{
$summe = 0;
foreach ($liste as $posten) {
$summe += postenSumme($posten);
}
return $summe;
}
function baueRechnung(array $liste, string $nummer): string
{
$zeilen = [];
foreach ($liste as $posten) {
$zeilen[] = zeileFuerPosten($posten);
}
$zeilen[] = "Rechnung " . $nummer . ": " . count($liste) . " Posten, "
. number_format(gesamtsumme($liste) / 100, 2, ",", ".") . " EUR";
return implode("\n", $zeilen);
}
echo baueRechnung($liste, "2026-004"), "\n"; Die Ausgabe ist Zeichen für Zeichen dieselbe. Verändert hat sich nur, wie der Code aussieht.
Jede der drei neuen Funktionen hat genau einen Zweck, und der steht in ihrem Namen.
postenSumme() rechnet, zeileFuerPosten() formatiert, gesamtsumme() addiert. baueRechnung()
gibt es weiterhin, aber sie tut jetzt nur noch eines: Sie setzt die Teile zusammen.
Sieh dir an, was dabei nebenbei passiert ist: Der Rechenweg menge * einzel steht nur noch an
einer Stelle, und beide anderen Funktionen holen ihn sich von dort. Zwei Stellen können
auseinanderlaufen, eine nicht.
Der zweite Gewinn kommt später. In Abschnitt 17 schreibst du Tests, und ein Test besteht darin, eine
Funktion mit bekannten Werten aufzurufen und ihr Ergebnis zu vergleichen. postenSumme() lässt sich
so prüfen. Die lange Fassung von oben nicht: Sie liefert eine fertige Rechnung als Text, und wenn
daran etwas nicht stimmt, weißt du nicht, welcher der drei Teile schuld ist.
Prüfe zuerst, was dagegen spricht
<?php
declare(strict_types=1);
// Verschachtelt:
function pruefeTief(string $name, int $alter): string
{
if ($name !== "") {
if ($alter > 0) {
if ($alter < 130) {
return "in Ordnung";
} else {
return "Alter zu hoch";
}
} else {
return "Alter fehlt";
}
} else {
return "Name fehlt";
}
}
// Mit fruehen Rueckgaben:
function pruefeFlach(string $name, int $alter): string
{
if ($name === "") {
return "Name fehlt";
}
if ($alter <= 0) {
return "Alter fehlt";
}
if ($alter >= 130) {
return "Alter zu hoch";
}
return "in Ordnung";
}
echo pruefeTief("Mia", 34), " / ", pruefeFlach("Mia", 34), "\n";
echo pruefeTief("", 34), " / ", pruefeFlach("", 34), "\n";
echo pruefeTief("Ben", 0), " / ", pruefeFlach("Ben", 0), "\n";
echo pruefeTief("Tom", 200), " / ", pruefeFlach("Tom", 200), "\n"; Beide Funktionen liefern dieselben vier Antworten. Die erste schachtelt vier Ebenen tief, die zweite ist flach.
Der Trick heißt frühe Rückgabe: Prüf am Anfang alles, was gegen ein Ergebnis spricht, und geh
per return sofort wieder raus. Was danach steht, ist der Normalfall, und der braucht keine
Einrückung mehr.
Dabei ändert sich auch die Reihenfolge, in der man liest. In der tiefen Fassung findest du den Normalfall ganz innen, vier Ebenen weit rechts. In der flachen steht er zuletzt und ganz links.
Nebenbei wirst du die else-Blöcke los. Nach einem return braucht es kein else, denn die
Funktion ist an dieser Stelle ohnehin schon vorbei.
Kleine Bausteine lassen sich anders zusammensetzen
<?php
declare(strict_types=1);
function postenSumme(array $posten): int
{
return $posten["menge"] * $posten["einzel"];
}
$liste = [
["name" => "Kabel", "menge" => 3, "einzel" => 990],
["name" => "Maus", "menge" => 1, "einzel" => 2490],
["name" => "Tastatur", "menge" => 2, "einzel" => 4365],
];
$summen = [];
foreach ($liste as $posten) {
$summen[] = postenSumme($posten);
}
echo "Teuerster Posten: ", number_format(max($summen) / 100, 2, ",", "."), "\n";
echo "Zusammen: ", number_format(array_sum($summen) / 100, 2, ",", "."), "\n";
echo "Im Schnitt: ", number_format(array_sum($summen) / count($summen) / 100, 2, ",", "."), "\n"; Hier steht dieselbe postenSumme() wie vorher, und dahinter drei Fragen, die vorher gar nicht zu
beantworten waren: Was ist der teuerste Posten, was kosten alle zusammen, was ist der Schnitt.
Das ist der eigentliche Ertrag des Aufteilens. Die lange Fassung konnte genau eine Sache: eine fertige Rechnung als Text liefern. Wolltest du nur die Summe, müsstest du die Rechnung bauen und die letzte Zeile wieder auseinandernehmen.
Deshalb noch einmal die Regel aus Lektion 6.1, jetzt mit einem zweiten Grund: Gib zurück, statt auszugeben. Ein Baustein, der etwas liefert, lässt sich in einen anderen einsetzen. Ein Baustein, der etwas ausgibt, kann nur an einer einzigen Stelle im Programm stehen.
Zum Mitnehmen
Eine Funktion zu schreiben ist leicht. Sie klein zu halten ist die Arbeit. In dieser Lektion geht es nicht um neue Syntax, sondern um die Frage, wann ein Block zu viel tut und wie man ihn auseinandernimmt, ohne dass sich am Ergebnis etwas ändert.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 4 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.