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.
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.
Hey Phil,
freut mich, danke für deinen Kommentar!
Viele Grüße
Florian
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 🙂
Wie gut! 😀
Jap, das stimmt, guter Punkt.