Archiv der Kategorie: art of code

DRY – redundante Funktionen in PHP vermeiden

DRY – Don’t repeat yourself.
Das kennt jeder, das _sollte_ jeder wissen. Und so gut wie jeder, der etwas Ahnung hat, versucht es so oft es geht umzusetzen.

Allerdings: Das gilt nicht nur für Funktionsnamen, das DRY-Prinzip geht weiter. Es meint IMHO auch, dass man für viele ähnliche Abfragen sich am besten Wrapper baut, die dann die Anfrage an eine Funktion leiten, die die eigentliche Antwort produziert. Die Wrapper „füttern“ die eigentliche Funktion nur mit den richtigen Fragen.

Ein Beispiel: Siggi Scriptkid hat ein Klasse zur Benutzerverwaltung geschrieben, die braucht er in seinem aktuellen Projekt. Er speichert seine Benutzerdaten in einer MySQL Datenbank und muss nun seiner Klasse folgende Fragen stellen und erwartet folgende Antworten:

  • Hole mir den Benutzer mit der ID 5
  • Hole mir den Benutzer mit der Mailadresse xyz
  • (noch viele weitere, ähnliche Fragen mehr)

Siggi schreibt also ganz fleißig Funktionen in seine Klasse:
(Stark vereinfacht, es fehlen Filtern der Usereingaben, maskieren der SQL String und vieles mehr; der Code dient nur als Beispiel!)


class User
{
public function getUserByID($userid)
{
$ressource = mysql_query('SELECT vorname, name
FROM user
WHERE userid = '.$userid);
$user = mysql_fetch_assoc($ressource);
return $user;
}

public function getUserByMailadress($email)
{
$ressource = mysql_query('SELECT ID, mail, userlevel
FROM user
WHERE mail = "'.$email.'"');
$user = mysql_fetch_assoc($ressource);
return $user;
}
}

Nun trifft man auf folgendes Problem: Die Felder der jeweiligen Rückgabe sind unterschiedlich. Auch wenn Siggi nun hingeht, und in beiden Funktionen die Felder gleich setzt, ergeben sich Wartungsschwierigkeiten:

  • Was ist, wenn noch mehr, ähnliche Funktionen hinzukommen? Dann bekommt Siggi wirklich Arbeit!
  • Was ist, wenn Siggi der „user“ Tabelle noch mehr Felder hinzufügen muss und es 6 Funktionen gibt?
  • Was ist, wenn Siggi auch nur eine der Funktionen vergisst?

Um es kurz zu machen: Hier greift das DRY-Prinzip NICHT! Der o.a. Code sollte einem Entwickler, der nicht Siggi Scriptkid heißen möchte, nicht passieren.

Aber wie wende ich das DRY-Prinzip nun an?
Okay, ich schreibe mal meinen Vorschlag dazu:


class User
{
public function getUserByID($userid)
{
return $this->getUser('ID='.$userid);
}

public function getUserByMailadress($email)
{
return $this->getUser('mail='.$email);
}

private function getUser($where)
{
$ressource = mysql_query('SELECT ID, vorname, name, mail, userlevel
FROM user
WHERE '.$where.'
LIMIT 1);
$user = mysql_fetch_assoc($ressource);
return $user;
}
}

Ich schaffe eine private Methode, die die Daten aus der Datenbank holt. Diese ist die einzige Methode, die das macht, somit habe ich nur eine Stelle, in der ich zukünftig Felder ändern muss, wenn sich welche ändern.

Die „alten“ Funktionen verbleiben als Wrapper-Funktionen und übergeben der privaten Funktion nur die richtigen Parameter, um ihre eigene Aufgabe erfüllen zu können. Dann leiten Sie Antwort einfach als eigene wieder zurück.

Auf diesem Wege verinfache ich die Pflege, bin in der Lage, meine alten Funktionen weiterhin nutzen zu können – mit dem Vorteil, dass nun alle Antworten einheitlich sind – und bin darüberhinaus in der Lage, sehr schnell verschiedene neue Funktionen zu schreiben, die mit neuen Fragestellungen klarkommen, z.B. Benutzer mit E-Mail x und Passwort y.

Was haltet ihr von dieser Vorgehensweise. Ich selbst halte sie für praktiabel und gut, was meint ihr? Gut? Schlecht? Gibt es bessere Vorgehensweisen?

Achtung: Newsletter-Problem

Quizfrage: Wie speichere ich folgendes, zugegebenermaßen sehr komplexe und nicht grad oft vorkommende Szenario in einer MySQL-Datenbank ab:

Es soll gespeichert werden, ob ein Nutzer zustimmt, einen Newsletter zu bekommen oder nicht

Ja, ich weiß, realitätsfern und Spezialanwendung, akademischer Aspekt und und und. Schon klar, aber als Denksportaufgabe für angehende Doktoren in Theopraktischer-Quanteninformatik bestimmt ein netter Tagesfüller.

Ein möglicher Ansatz – nochmal: wir reden hier über wirklich experimentelle Ansätze, die noch nie ein Mensch zuvor … usw, also weiter – also ein möglich Ansatz wäre folgender:

Man benutze ein Feld vom Typ varchar. Varchar, weil man nie weiß, ob sich evtl. mal die Anforderung ändern könnte. Stichwort hierbei: Skalierbarkeit! Denn wie schnell ändert sich so eine Anforderung und sollte man dann den falschen Datentypen ausgewählt haben (weil man natürlich mal wieder mit primitiven Ansätzen fern jeder akademischen Laufbahn daran gegangen ist und dachte, man könnte als Laie ein derart komplexes System auch nur im Ansatz verstehen), dann, ja dann dampft die Kacke! Also, varchar.

Und auch die Größe des varchar soll und muss disktutiert werden. Ein sicherlich guter Ansatz wäre, das Feld auf 255 Zeichen zu begrenzen – im Spezialfall kann das natürlich noch nach oben korrigiert werden, was aber wieder zu lasten der Konfiguration der Seitlichen Ausrichtung gehen kann und wird und nicht sollte.

Den Verschiedenen Zuständen geben wir die folgenden Namen: ‚active‘ beschreibt den Zustand des „wollenden Empfängers“ (genaue Definition erfolgt in einer Subklasse) und ‚inactive‘ den des dazu konträhren Zustandes. Für alle anderen wählen wir “ als den wohlbekannten Universaloperator auf der Menge der möglichen Zustände über „Ja“ und „Nein“ (präziser wäre ein oder, was aber im allgemeinen Sprachgebrauch eher verwirrt war und nicht mehr an seinen Platz fand. Das und war so nett und sprang kurzfristig ein.).

Wie man nun sieht, kann man selbst komplexe Vorgänge be- und verarbeiten, wenn man nur ruhig und logisch an die Sache heran geht und die Aufgaben in kleinen Abschnitten betrachtet (ist auch viel einfacher, eine Lupe zu benutzen anstelle eines Verkleinerungsglasses.).

Der Vollständigkeit halber sei noch folgender Zwischenruf erwähnt, der einen Eklat innerhalb des Beratungsgremiums hervorrief, als ein hochnäsig-arronanter Programmierer in geistiger Verwirrung den Satz ausrief:

Man benutzt ein (tiny)int-Feld und 0 und 1 zum speichern der Zustände Nein und Ja!

Unvorstellbar, wie dumm doch manche sind…

Füllen von array-Werten in Reihenfolge: for vs range

Will man ein Array mit Werten füllen, die in einer Reihenfolge stehen (a-x, 1-20, usw.), dann gibt es dazu die Funktion “range”, aufruf mit

$reihenfolge = range(1,20);

Dabei erhält man ein Array mit Werten von 1 – 20.
Die alternative ist, das ganze “manuell” zu erledigen, sprich in unserem Beispiel 1-20 wäre das:

$reihenfolge=array();
for ($i=1;$i<=20;$i++)
array_push($reihenfolge,$i);

Meine ursprüngliche Fassung war Nummer 2 bis ich auf die Funktion “range” stieß. Und die muss ich natürlich gleich ausprobieren und Performance-Tests machen. “Muss doch mit einer PHP-Funktion schneller sein wie mein kindlich-naiver Ansatz!”, denke ich mir so, denn eine Zeile gegen 3 Zeilen, dazu noch von PHP-Spezialisten verfasst und – wahrscheinlich – optimiert bis zum maximal Möglichen, dass _muss_ doch schneller gehen!
Aber was ist nun schneller, range oder for?
Meine Tests habe ich mit dem Zend Studio vorgenommen, hier das Durchschnittliche Ergebnis von vielen Messungen:

range
for
1-20
0.047ms
0,006ms

Hätte ich persönlich nicht vermutet, aber meine Kindlicher Ansatz ist wirklich schneller und zwar im Durchschnitt 7x schneller.

[UPDATE 28.06.2010]

Danke an Daniel und seine „Nachuntersuchung“ meines Artikels, dabei habe ich noch einen Fehler in meiner Auswertung gefunden, der mich wirklich zu meinem gezeigten Ergebnis bringt.

Zur Überprüfung habe ich nochmal einen eigenen Code geschrieben, der range, array_push und [] überprüft und aufzeit: range ist wirklich um ein vielfaches schneller wie die beiden anderen Methoden! Daniel, du hast ja recht 😉

Hier mal der Testcode:


$anzahl_der_werte = 99999;

$start1=microtime(1);
$stack1=array();
for ($i=1;$i<=$anzahl_der_werte;$i++)
array_push($stack1,$i);
$ende1=microtime(1);

$stack2=range(1,$anzahl_der_werte,1);
$ende2=microtime(1);

$stack3=array();
for ($i=1;$i<=$anzahl_der_werte;$i++)
$stack3[]=$i;
$ende3=microtime(1);

echo "Anzahl der Durchgaenge: " . $anzahl_der_werte . "\n";
echo "Zeit array_push: " . ($ende1-$start1) . "\n";
echo "Zeit range: " . ($ende2-$ende1) . "\n";
echo "Zeit []: " . ($ende3-$ende2) . "\n";
exit;

Und das Ergebnis:


Anzahl der Durchgaenge: 99999
Zeit array_push: 0.36060690879822
Zeit range: 0.013633966445923
Zeit []: 0.33861804008484

Man sieht also sehr gut, dass range wirklich schneller ist, das ganze hält sich auch bei kleinen und großen Werten, man muss sich also nur merken, dass man bei sowas range nimmt.

Templates einbinden mit webEdition

Das schöne an webEdition ist, dass man sog. Mastertemplates verwenden kann. Das noch viel schönere ist, dass diese Mastertemplates wiederrum wieder Mastertemplates haben können – die Master rufen sich dann sozusagen rekursiv auf, bis es ein “normales” Template gibt, dass die Inhalte eingibt.

Auch schon gelöst ist, dass man in einem webEdition Template andere Templates einbinden kann – mittels we:include, dem man entweder die ID des Template gibt, dass eingebunden werden soll oder eben den sog. path, also den Pfad inklusive Namen und Erweiterung des Templates. Schöne Sache, das.

Weniger schön ist dagegen ein webEdition Projekt, welches – bedingt durch über ein halbes dutzend Sprachen und vielen, vielen Vorlagen – eine 3 stellige Template Zahl beinhaltet und das ist unzählbar vielen Ordnern. Wow.

Und nun der eigentliche Punkt: Ein Blick in das deutsche Mastertemplate zeigt:

Okay, und nun? Ich muss eine Änderung an diesem Template durchführen, aber welches ist nun Template 1308? Es bleibt einem nichts anderes übrig, als mit der Maus über jedes Element zu fahren, um im Hover dann die ID zu sehen. #Nerv.

Daher: Bitte, bitte, liebe webEdition Template Editoren: Benutzt NICHT das ID-Tag, nehmt das path-Attribut des we:include.

Ich finde, das hier sieht schon viel freundlicher aus und läßt sich, zumindest meiner bescheidenen Meinung nach, viel besser und schneller finden:

Ist zwar mehr Tipparbeit, aber grade im Team sehr hilfreich. Denkt auch daran, was passiert, wenn ihr nach 2 Jahren mal wieder so ein Projekt pflegen müsst und zwischendurch 18 andere Projekte bearbeitet wurden … ihr blickt wahrscheinlich selbst nicht mehr durch und fragt euch, welcher Idiot da das ID-Tag ben…oh, das war ja ich *ups*

In diesem Sinne…

Benutzereingaben prüfen: Leere Strings

Ich kenne viele Wege, einen String daraufhin zu prüfen, ob der User

a) überhaupt was eingegeben hat und
b) ob der User außer Leerzeichen was eingegeben hat.

In jeder Sprache gibt es dafür viele Wege, einige gut, viele nicht so gut.

Heute mal ein Beispiel aus der Reihe “not so good”:

if

(strlen($tls_description) > 1 && substr_count($tls_description,“ „) >= (strlen($tls_description) – substr_count($tls_description,“ „)))
{
   $err = true;
}

Mein Ansatz, genauso gut, dafür lesbar und ebenso “effektiv”:

if

(strlen(trim($tls_description))<1)
{
   $err = true;
}

Nicht nur schneller, sondern auch lesbarer und nicht so WTF?

Art of Code, Refactoring, Sinn und Unsinn von Funktionen

Gegeben – aus welchem Grund auch immer – sei folgender Code:

function getRights(){
  $arr_rights = array();
  array_push($arr_rights, ‚admin‘);
  array_push($arr_rights, ‚client‘);
  array_push($arr_rights, ’superadmin‘);
  return $arr_rights;
}
function getRights_Superadmin(){
  $zws_rights = array();
  $zws_rights = getRights();
  return $zws_rights[2];
}
function getRights_Admin(){
  $zws_rights = array();
  $zws_rights = getRights();
  return $zws_rights[0];
}
function getRights_Client(){
  $zws_rights = array();
  $zws_rights = getRights();
  return $zws_rights[1];
}

Die Aufrufe der Funktionen getRights_Client(), …Admin() und …Superadmin() kommen im gesamten Script 16 mal vor, insgesamte Laufzeit der Datei, in denen diese Funktionen sind, liegt bei 0,113ms.

1

Folgendes Problem ergibt sich:
Bei jedem Aufruf einer der 3 Funktionen wird jedes Mal die Funktion getRights() aufgerufen, ergo wird diese Funktion 16 mal gerufen und verbraucht damit eine Laufzeit von 0.046ms, allein durch diese 16 Aufrufe.
An dieser Stelle – mal abgesehen davon dass es einfach “bad style” und ziemlich unoptimiert ist – sollte man refactorisieren.

Zuerst muss man den Sinn und Zweck verstehen, also: Was machen die Funktionen und warum?

Was machen die Funktionen?
Die Aufrufe der 3 Funktionen geben den Namen des jeweiligen aktuellen Benutzerrechtes wieder, der Name spiegelt sich in einer Datenbank wieder (ja, auch das hätte man anders machen können und sollen, aber dafür bleibt bei einer Bearbeitung von bestehendem Code nicht soviel Zeit) und diese Namen müssen auch 1:1 übernommen werden, will Mensch nicht den ganzen Code ändern (wozu Mensch normalerweise keine Zeit hat).

Welchen Sinn hat das ganze?
Die Funktion wie Sie oben stehen haben schon einen – wie ich meine – akademischen Sinn. Ziel des ganzen war es bestimmt, die jeweiligen Rechte einmal festzulegen und diese dann, mithilfe der Funktionen, an vielen Stellen abzufragen und zu vergleichen.

Der Weg über Funktionen und immer wieder kehrende Aufrufe anderer Funktionen ist jedoch im besten Falle als unpassend zu bezeichnen. Sicher: Es funktioniert, allerdings sollte ein guter Entwickler auch immer versuchen, seinen Code nicht nur funktional zu tippen, sondern auch “gut” und effizient.
Und effizient ist dieser Code nicht.

Kommen wir also zur Verbesserung: Wir refactorisieren den Code!

Aus Funktion und Sinn kann man leicht ersehen, dass das Mittel der Wahl am besten mit Konstanten umgesetzt werden kann.

Dazu definieren in der Datei im Kopfbereich diese Konstanten:

define(‚RIGHT_CLIENT‘, ‚client‘);
define(‚RIGHT_ADMIN‘, ‚admin‘);
define(‚RIGHT_SUPERADMIN‘, ’superadmin‘);

Nun ändern wir zuerst die Funktion getRights() entsprechend ab:


function getRights() {
  $arr_rights = array();
  array_push($arr_rights,RIGHT_ADMIN);
  array_push($arr_rights,RIGHT_CLIENT);
  array_push($arr_rights,RIGHT_SUPERADMIN);
  return $arr_rights;
}

Und nun können wir in den einzelnen Funktionen die Konstanten ins Spiel bringen.
Dadurch entfallen natürlich auch die Aufrufe von getRights() innerhalb der Funktionen, was eine Entlastung der Aufrufe von getRights() von 8 Aufrufen erbringt:

function getRights_Superadmin() {
  return RIGHT_SUPERADMIN;
}
function getRights_Admin() {
  return RIGHT_ADMIN;
}
function getRights_Client() {
  return RIGHT_CLIENT;
}
Und nun wird wieder profiliert und es ergibt sich folgendes Bild:
2

Nun wird die Funktion getRights() gar nicht mehr aufgerufen (der Aufruf erfolgt aber an anderen Stellen noch, die Ausführzeit nur dieser einen Datei liegt nun bei 0,054ms, also knapp 50% unter der eigentlichen Zeit und die 3 Funktionen haben ebenfalls verkürzte Ausführzeiten.

Die Funktionen selbst sind erhalten geblieben, der “Sinn” auch, denn die Rechte können zentral verwaltet und geändert werden.

Der nächste Schritt wäre, im gesamten Script die Aufrufe von getRight_Client(), …Admin() und Superadmin() gegen die entsprechenden Konstanten zu tauschen, so dass kein Aufruf mehr an diese Datei gehen würde. Dies kann man machen, ohne dabei den “Sinn” oder Funktion zu beeinträchtigen, allerdings fällt das wieder unten den Punkt “Zeit, die einem keiner bezahlt”, aber sinnvoll aus Sicht der Refactorisierung und des “Good styled code” wäre es sicherlich.

Noch am Rande: Alle Profilierungen wurden mit dem Zend Studio 7 vorgenommen, die Screenshots entstammen den jeweiligen Reports der “Execution Statistic”.

Kommentare dazu sind gern gesehen 😉