Mehr Übersichtlichkeit im Code mit weniger `else` und Schachtelung

Ich habe mich kürzlich vermehrt damit beschäftigt, besseren Code zu schreiben, weil das ein Aspekt ist, bei dem ich noch einiges lernen möchte. Ein einfach zu merkender und – wie ich finde – schnell einleuchtender Weg zu weniger komplexem Code ist die Vermeidung von else und die Reduzierung von Verschachtelungen im Code.

Leider weiß ich nicht mehr, in welchem Artikel oder welchen Artikeln ich die Tipps und Ideen gelesen habe, über die ich hier schreibe. Ich bin ziemlich sicher, dass die lange Liste »Learning OOP in PHP« der Ausgangspunkt war, aber beim schnellen Überfliegen konnte ich den/die Artikel nicht ausmachen.

Aber jetzt zum Thema.

Warum else und tiefe Schachtelungen vermeiden?

Weil else Code komplexer macht, genauso wie tiefe Schachtelungen (die gerne mit durch else zustande kommen). Nehmen wir einen Ausschnitt aus meinem Lazy-Loader-Plugin, der dazu da ist zu prüfen, ob ein Request im Backend stattfindet:

<?php function is_admin_request() { // […] if ( 0 === strpos( $current_url, $admin_url ) ) { if ( 0 === strpos( $referrer, $admin_url ) ) { return true; } else { if ( function_exists( 'wp_doing_ajax' ) ) { return ! wp_doing_ajax(); } else { return ! ( defined( 'DOING_AJAX' ) && DOING_AJAX ); } } } else { if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) { return false; } return ( isset( $_REQUEST['context'] ) && 'edit' === $_REQUEST['context'] ); } }
Code-Sprache: PHP (php)

Wenn ich da heute drauf gucke, kriege ich halbe Zustände, aber vor ein paar Jahren schien das für mich ein guter Weg zu sein, und für diesen Artikel ist es ein gutes Beispiel.

Schauen wir jetzt auf den Code, der dasselbe macht, aber ohne else:

<?php function is_admin_request() { // […] if ( 0 === strpos( $current_url, $admin_url ) ) { if ( 0 === strpos( $referrer, $admin_url ) ) { return true; } if ( function_exists( 'wp_doing_ajax' ) ) { return ! wp_doing_ajax(); } return ! ( defined( 'DOING_AJAX' ) && DOING_AJAX ); } if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) { return false; } return ( isset( $_REQUEST['context'] ) && 'edit' === $_REQUEST['context'] ); }
Code-Sprache: PHP (php)

Immer noch ziemlich viele Bedingungen, aber doch schon weniger komplex und verschachtelt als vorher, oder?

Gut, das war jetzt gleich natürlich ein recht schlimmes Beispiel. Aber auch wenn in einer Funktion auf oberster Ebene nur das hier stehen würde:

if ( function_exists( 'wp_doing_ajax' ) ) { return ! wp_doing_ajax(); } else { return ! ( defined( 'DOING_AJAX' ) && DOING_AJAX ); }
Code-Sprache: JavaScript (javascript)

Wäre der Code ohne else doch weniger komplex:

if ( function_exists( 'wp_doing_ajax' ) ) { return ! wp_doing_ajax(); } return ! ( defined( 'DOING_AJAX' ) && DOING_AJAX );
Code-Sprache: JavaScript (javascript)

Und wie kann es vermieden werden?

Ich arbeite momentan mit drei »Werkzeugen«, wenn ich es mal so nennen will. Wenn man sie kennt und sich erst mal des Problems der Komplexität mit zu vielen Verschachtelungen und else bewusst ist, eigentlich alle einleuchtend und ziemlich einfach zu nutzen.

Frühe return-Statements

Dafür war eigentlich oben im Code schon alles vorbereitet, es war mir damals nur nicht bewusst. Wenn von einer Funktion oder Methode ein Wert zurückgegeben wird, gibt es oft Fälle, dass dieser Rückgabewert unter bestimmten Bedingungen schon früher feststeht.

Ist das der Fall, einfach direkt mit return zurückgeben und damit die Ausführung der Funktion an der Stelle beenden. Dadurch muss danach nicht auf den anderen Fall (else) geprüft werden, weil die Funktion gar nicht mehr weiter ausgeführt wird.

Im Beispiel unten endet die Funktionsausführung in Zeile 6, wenn die ersten beiden Bedingungen gegeben sind. Das else in Zeile 7 hat also nicht nur zu komplexerem Code geführt, sondern war auch noch komplett ohne Funktion 😀

<?php function is_admin_request() { // […] if ( 0 === strpos( $current_url, $admin_url ) ) { if ( 0 === strpos( $referrer, $admin_url ) ) { return true; } else { // […] }
Code-Sprache: PHP (php)

Sinnvoll wäre dieses else (und die anderen) in dem Fall, dass da statt eines return nur eine Zuweisung steht, beispielsweise so:

<?php function is_admin_request() { // […] $admin_request = null; if ( 0 === strpos( $current_url, $admin_url ) ) { if ( 0 === strpos( $referrer, $admin_url ) ) { $admin_request = true; } else { // […] return $admin_request; }
Code-Sprache: HTML, XML (xml)

Hier wird eine Flag-Variable eingesetzt ($admin_request) und erst am Ende zurückgegeben. Durch ein frühes return, beziehungsweise auch mehrere, kann man hier schon einiges an Komplexität einsparen und wenn es gut läuft sogar alle else umgehen.

Standard-Werte

Gestern erst habe ich kurz im Editor folgenden Code in einer Funktion stehen gehabt, die sich um die Prüfung einer Checkbox-Gruppe in einem Formular kümmern soll:

if ( ! isset( $social_link['checked'] ) ) { $social_link['checked'] = false; } else { $social_link['checked'] = true; }
Code-Sprache: PHP (php)

Umgehen lässt sich das else hier, indem wir vorher einen Standard-Wert setzen und den dann bei zutreffender Bedingung ändern:

$checked = true; if ( ! isset( $social_link['checked'] ) ) { $checked = false; } $social_link['checked'] = $checked;
Code-Sprache: PHP (php)

Code-Teile in eigene Funktionen oder Methoden auslagern

Wenn wir den Fall haben, dass wir trotz Einsatzes früher return-Statements immer noch ein bisschen schachtelig unterwegs sind, wie oben bei dem Test auf einen Admin-Request, können wir das mit dem Erstellen einer Funktion/Methode lösen, in die wir einen Teil des Codes auslagern:

<?php function is_admin_request() { // […] if ( 0 === strpos( $current_url, $admin_url ) ) { if ( 0 === strpos( $referrer, $admin_url ) ) { return true; } return ! is_ajax_request(); } if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) { return false; } return ( isset( $_REQUEST['context'] ) && 'edit' === $_REQUEST['context'] ); } function is_ajax_request() { if ( function_exists( 'wp_doing_ajax' ) ) { return wp_doing_ajax(); } return defined( 'DOING_AJAX' ) && DOING_AJAX; }
Code-Sprache: PHP (php)

Hier habe ich die beiden Prüfungen auf einen AJAX-Request in eine eigene Funktion ausgelagert, womit is_admin_request() noch etwas übersichtlicher geworden ist. Theoretisch könnten wir das eine letzte geschachtelte if-Statement los werden, indem wir beide Bedingungen in einem if prüfen:

function is_admin_request() { // […] $is_admin_url = 0 === strpos( $current_url, $admin_url ); if ( $is_admin_url && 0 === strpos( $referrer, $admin_url ) ) { return true; } if ( $is_admin_url ) { return ! is_ajax_request(); } if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) { // […]
Code-Sprache: PHP (php)

Ich weiß nicht, ob mir das besser gefällt als die andere Variante, weil wir dafür jetzt eine komplexere Bedingung haben als vorher, aber es wäre eine Möglichkeit.

Ebenfalls hilfreich ist die Auslagerung von einem oder mehreren Code-Teilen in eine separate Funktion oder Methode, wenn wir kein frühes return nutzen können und deshalb eigentlich ein else einsetzen müssten, weil beispielsweise nach den Prüfungen die Funktion/Methode noch nicht vorbei ist. Die ausgelagerte Funktion kann dann vielleicht wieder mit einem frühen return für bestimmte Fälle beendet werden.

Fazit

Ich persönlich finde, dass der Versuch, möglichst viele else und eine zu starke Verschachtelung des Codes zu vermeiden, sehr dabei hilft, übersichtlicheren und weniger komplexen Code zu schreiben. Natürlich wird sich nicht jedes else vermeiden lassen, und dogmatisch zu sagen, dass nur eine Einrückungsebene in einer Funktion erlaubt sein soll, ist vielleicht etwas zu viel des Guten, aber solche Richtlinien im Kopf zu haben hilft. Zumindest mir 🙂

Die Sache mit dem frühen return lässt sich übrigens auch auf Schleifen übertragen, da werden dann continue und break genutzt.

5 Reaktionen zu »Mehr Übersichtlichkeit im Code mit weniger `else` und Schachtelung«

  1. Sehr hilfreicher Beitrag.

    Ich habe mich nun auch seit geraumer Zeit an den Coding Style gehalten und gerade bei komplexen Projekten macht einen erheblichen Unterschied.
    Doch auch dem Klassiker, dass man seinen eigenen Code nach einigen Wochen nicht mehr versteht, wird damit vorgegriffen. Weil der Code einfach lesbarer und nachvollziehbarer ist.

    Klasse, dass du das noch einmal aufgegriffen und gut zusammengefasst hast.

  2. Passend dazu habe ich mal den Spruch gehört: "Guter Code steht links!"

    Je nachdem, was in den Funktionen alles passiert kann ein frühes "rausgehen" via return auch ein Performance-Gewinn sein.

    Gerade wenn die Funktion jedes mal bei jedem Request abfeuert ist es wichtig nichts unnötiges auszuführen.

    Auf jeden Fall eine gute Zusammenfassung und Erinnerung 🙂

    1. »Passend dazu habe ich mal den Spruch gehört: ›Guter Code steht links!‹«

      Wie gut! 😀

      »Je nachdem, was in den Funktionen alles passiert kann ein frühes ›rausgehen‹ via return auch ein Performance-Gewinn sein.

      Jap, das stimmt, guter Punkt.

Erwähnungen

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.