In letzter Zeit habe ich mehrfach bei verschiedenen kleineren Projekten, v.a. im PHP-Bereich gelesen, dass sogenannte “Coding Guidlines” entworfen bzw. einfach festgesetzt werden. Ob diese nun jedoch in der Praxis wirklich umsetzbar bzw. sinnvoll sind, darüber machen sich nur die wenigsten Gedanken und wundern sich, warum die anderen Entwickler meckern oder warum sie selbst Probleme bei der Einhaltung haben.
Grundsätzlich lässt sich jedoch sagen, dass Coding Guidlines, d.h. Abmachungen über den für das Projekt einzuhaltenden Programmierstil, äußert sinnvoll sind, da es ja durchaus zu Problemen führen kann, wenn jeder nach seinem Stil arbeitet und nun zwei Personen nacheinander (oder noch schlimmer: nebeneinander) an der gleichen Datei arbeiten sollen.
Auch lassen sich durch einen durchdachten Programmierstil einige Fehler vermeiden, welche jedoch syntaktisch korrekt sind und deswegen nur äußerst schwer zu finden sind. Bei kommerziellen Projekten kann ein solcher Fehler viel Geld kosten!
if ($user->isAdmin) doSomeAdminThings();
Dieser Code ist syntaktisch korrekt und arbeitet auch so, wie es erwartet wird. Wenn Adminrechte verfügbar sind, dann wird die Funktion doSomeAdminThings() ausgeführt. Wenn wir nun jedoch nach einiger Zeit eine weitere Funktion einfügen, so kann dies zu unersichtlichen Fehlfunktionen führen.
if ($user->isAdmin) doSomeAdminThings(); doSomeAdminWork();
Beim Überfliegen des Quellcodes wird dieser Fehler nur von den wenigsten bemerkt. Jeder erwartet, dass sowohl doSomeAdminThings() als auch doSomeAdminWork() lediglich ausgeführt werden, wenn der Benutzer Administrator ist. Doch kann man nun feststellen, dass die Funktion doSomeAdminWork() immer ausgeführt wird, egal ob der User Admin ist oder nicht. Grund hierfür ist, dass lediglich ein Befehl von der If-Bedingung ohne Klammern umschlossen wird.
if ($user->isAdmin) { doSomeAdminThings(); doSomeAdminWork(); }
Forderung: Um Probleme im Programmfluss durch fehlerhafte Programmierung zu vermeiden sollten alle Kontrollstrukturen immer mit Klammern versehen werden.
Als erstes zum Thema “bis ins letzte Detail” mit Kommentaren versehen.
// Addition von a und b $c = $a + $b; // Ausgabe der Summe von $a und $b, repräsentiert durch die Variable $c echo $c;
Ich denke jedem wird bereits beim ersten Betrachten klar, dass hier eindeutig übertrieben wurde, da hier eindeutig ohne Probleme erkannt werden kann was passiert.
... for ($i=count($itemsOfVar) - count($itemsOfOthers); $i<count(base::getObj('objects')->userCounter); $i = $i * 2) ...
Eine solche Quellcode-Zeile, welche den Programmierer auf den ersten Blick mit Informationen erschlägt, sollte unbedingt kommentiert werden. Wenn jemand Fremdes diesen Code sieht, so wird er nur mit einem größeren Zeitaufwand erschließen können, was genau geschieht.
Forderung: Beim Kommentieren sollte der gesunde Menschenverstand genutzt werden. Es sollte nicht übertrieben werden, jedoch kann man grundsätzlich sagen, dass eine Kommentarzeile zu viel nicht so sclimm ist, wie eine Kommentarzeile zu wendig!
public function addDatabase(abstractDatabaseObject $dbObj) { ... }
Nun können an die Funktion addDatabase nur noch Objekte von Klassen übergeben werden, die von abstractDatabaseObject erben, es ist also sichergestellt, dass es sich um ein DatabaseObject handelt. Natürlich könnte man nun auch innerhalb der Funktion mit if-Abfragen usw. überprüfen ob es sich wirklich um ein solches handelt, der Aufwand wäre jedoch im Vergleich gigantisch. Auch sind Type-Hints sehr nützlich wenn man eine IDE wie etwa NetBeans einsetzt, da nun die Quellcode-Vervollständigung erkennt, von welchem Type eine Variable ist.
Forderung: Type-Hints müssen bei allen Funktionen verpflichtend eingesetzt werden!
Forderung: PHP-Skripte werden immer mit <?php begonnen. Bei Dateien die lediglich Klassen enthalten wird kein abschließender PHP-Tag verwendet.
Forderung: Temporäre Variablen werden durchgehend klein geschrieben, mehrere Wörter werden mit Unterstrichen getrennt. Klassenvariablen werden beginnen mit einem Kleinbuchstaben wenn sie Private oder Protected sind, ansonsten mit einem Großbuchstaben, mehrere Wörter werden durch Großbuchstaben getrennt. Konstante werden durchgehend groß geschrieben.
Das folgende Beispiel soll den Nutzen dieser Methode verdeutlichen:
class flugzeug { protected $flugzeugName; protected $pilotName; public function getFlugzeugName() { return $this->flugzeugName; } public function getPilotName() { return $this->pilotName; } public function setPilotName($name) { $this->pilotName = $name; } }
Während der Pilot eines Flugzeuges theoretisch jederzeit wechseln kann (=> Getter und Setter), wird der Name eines Flugzeuges nicht geändert (=> Getter, jedoch kein Setter). Würde man den Flugzeugnamen nun etwa im Konstruktor übergeben so könnte er für die gleiche Instanz der Klasse nicht mehr geändert werden, da keine Setter-Methode existiert.
Forderung: Nutzung von Getter-/Setter-Methoden für den Zugriff auf öffentliche Variablen einer Klasse sowie zur Datenkapselung.
Forderung: Wahrheitswerte dürfen, soweit nicht der komplette Funktionsablauf ein anderer ist, als Parameter übergeben werden.
Forderung: Für jede Klasse muss eine eigene Datei erstellt werden, welche den gleichen Namen wie die Klasse tragen sollte.
include("aaa.php"); include("aab.php"); ... include("zzz.php");
Nun argumentieren manche Programmierer, dass sie ja jede der eingebundenen Klassen unter bestimmten Umständen benötigen und sie deshalb einbinden müssen. Auch könne man die Klassen nicht erst bei der Benutzung per include einbinden, da es ja so vorkommen könnte, dass eine Datei mehrmals eingebunden wird. Dieses Problem lässt sich jedoch sehr leicht über eine Autoloader-Funktion lösen, wenn man für alle Klassen allgemeingültige Regeln bei ihrer Bennenung und Speicherung einhält. Sollte dies aufgrund zu komplexer Strukturen nicht möglich sein, so könnte man etwa auch mit einer Loader-Klasse arbeiten:
abstract class ClassHelper { private static $alreadyIncluded = array(); public static function includeClass($file) { if (!array_key_exists($file, self::$alreadyIncluded)) { include($file); self::$alreadyIncluded[] = $file; } } }
Forderung: Klassen sollen erst eingebunden werden, wenn sie vom Skript benötigt werden.
if (($user->isAdmin || $user->isMod) && (!$system->isEnabled && !$user->isLocked)) { }
Wesentlich übersichtlicher wäre es doch nun etwa so:
if ( ($user->isAdmin || $user->isMod) && (!$system->isEnabled && !$user->isLocked)) {}
Auch bei Arrays und Funktionen mit vielen Parametern sollte man mehrere Zeilen verwenden um die Übersichtlichkeit zu wahren. (Hinweis: Auch Strings können über mehrere Zeilen gehen!
Forderung: Die Grenze von 80 Zeichen pro Zeile sollte nur in Ausnahmefällen überschritten werden!
[...] Ja [...]
Sehr schöner Post, wenn für mich auch fast nichts neues dabei. Zwei kleine Anmerkungen:
Zu 6.: Wozu gibts __get() und __set() ?
Zu 8.: Warum nicht einfach __autload() benutzen?
Dafür sind diese magic mehtods gedacht, also warum nicht einfach benutzen?
Zu Punkt 6: In PHP gibt es einiges was man besser nicht nutzt, das so nebenbei erwähnt. Persönlich nutze ich _get und _set zwar auch als Teil meines OR-Mappers, was mir jedoch einigen Ärger mit meiner IDE eingebracht hat. Über die getXYZ() und setXYZ($value) Funktionen lässt sich der Zugriff auf die einzelnen Variableninhalte besser regulieren und es können leichter Zusatzaktionen ausgeführt werden. Natürlich könnte das auch mit generischen _get/_set-Methoden geregelt werden, würde aber bei größeren komplexen Klassen schnell unübersichtlich werden.
Zu Punkt 8: Autoload ist bei komplexen Ordnerstrukturen eigentlich absolut unsinnig, da, wenn viele verschiedene Dateipfade überprüft werden müssen, wesentlich mehr Ausführungszeit benötigt wird und auch eine höhere Festplattenbelastung entsteht.