mitmario.dev

Funktionen aufteilen

PHP Sandbox 3 Min Lesezeit 4 BeispieleLektion 5 von 8

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

Eine Funktion, die zu viel tut
<?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

Dieselbe Arbeit in vier Teilen
<?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

Früh zurück statt tief verschachtelt
<?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

Ein Baustein, drei Fragen
<?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, kostenlos

Zu 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.