Ein (nennen wir es mal) “indirekter Kollege” hat mir gegenüber angekündigt einen Beitrag in seinem Blog über “aussagekräftige Code” zu verfassen. OK, ich finde seine Beiträge nicht immer spannend, aber gelungen und sie haben eigentlich immer Hand und Fuß. Vorhin kam ich aber an einen Punkt, wo ich selber über die Aussagekraft von Code nachdenken musste.
Man betrachte einfach mal folgende kleine Klasse
class Zugriffskontrolle {
public function addRule ($benutzername, $hatZugriffsrecht) {
// some code
}
}
Das sieht ja noch recht niedlich aus und ergibt aus der Signatur vollkommen Sinn. Jetzt möchte man die Klasse benutzen
$zugriffskontrolle = new Zugriffskontrolle()
$zugriffskontrolle->addRule ('meinName',true);
Ist denn an dieser Stelle auf Anhieb klar, was mit true gemeint ist? OK, einfache Lösung: Strings verwenden. Dann muss man aber wieder aufpassen, dass nur zwei mögliche Strings übergeben werden (Boolean = Binär = 2 Möglichkeiten), bei der Verwendung von Boolean kann man allerdings im Zweifelsfall einfach auf Boolean casten und erlaubt somit auch die Übegabe von 0 und 1, falls dies jemand möchte. Nebenbei wären Strings bei zwei Möglichkeiten einfach oversized.
Das Beispiel mit einem boolschen Parameter war noch recht einfach. Der Grund, wieso ich überhaupt darüber nachdenke, ist eine Methode mit 3 boolschen Parametern. Das sieht dann schon recht fies aus
$bla->myMethod ('name',true,false,true);
Spätestens hier ist dann die Übersicht dahin und ständige Blicke in die API-Doc sind nicht besonders förderlich.
Nun aber zum aussagekräftigen Code. Wie gesagt wäre hier String, oder irgendwas anderes, was mehr als zwei Möglichkeiten bietet (Integer) oversized, einfach weil man es nicht braucht, aber absolut jeder andere Datentyp (ausser null) hat nun mal einen größeren Wertebereich. Die Lösung des Dilemmas ist denkbar einfach
class Zugriffskontrolle {
const ERLAUBEN = true;
const VERBIETEN = false;
public function addRule ($benutzername, $hatZugriffsrecht) {
// some code
}
}
Zwei triviale Konstanten. Der Vorteil davon wird erst bei der Verwendung erkennbar.
$zugriffskontrolle->addRule('myName',Zugriffskontrolle::ERLAUBEN);
Mal abgesehen davon, dass jetzt klar lesbar ist, was die Zeile bedeutet, bleibt auch weiterhin die Möglichkeit true oder (mit Casting) 1 zu verwenden, je nachdem, wie es sich der Entwickler denkt.
Meines Erachtens ist dieses Vorgehen nur zubefürworten. Es gibt keine nennenswerten Nachteile
- Konstanten werden bereits vom Parser ersetzt und beeinflussen deshalb die Laufzeit minimal
- Wenn ein Opcode-Cache (APC,..) verwendet wird, dann beeinflusst es die Laufzeit sogar nur, wenn der Cache neu gefüllt werden muss, weil im Obcode die Konstanten bereits ersetzt wurden.
- Da die Konstanten im Klassenkopf stehen, wird der Code nur unwesentlich unübersichtlicher. Man muss nur weiter nach unten scrollen
dahingegen gibt es Vorteile
- Bei der Verwendung der Klassen wird der Code lesbarer und verständlicher, weil die Konstante selbstredend ist
- Man zwingt niemand eine Konvention auf, weil die “alten” Werte (hier: Boolean) erhalten bleiben.
- Wenn statt Boolean ein anderer Datentyp, zum Beispiel String, verwendet würden (hier: “erlauben” und “verbieten”), kann ein kleiner Tippfehler ein schwer nachvollziehbares Fehlverhalten verursachen, zum Beispiel wenn nur auf einen Wert geprüft wird und der andere Wert implizit angenommen wird
Ausgerechnet (schade eigentlich..) mein geliebtes Zend Framework hat diesen Gedaken nur unzureichend umgesetzt. Passend zu meinem Beispiel ist es die Zend_Acl-Komponente, die zwar Konstanten für die Werte der Zugriffsrechte setzt, dabei allerdings die String allow und deny verwendet. Das hat zwar den kleinen Vorteil, dass der Code auch lesbar bleibt, wenn man den primitiven Typ anstatt der Konstante verwendet, aber die Lesbarkeit ist bei beiden Varianten nahezu identisch, weswegen es aus meiner Sicht wenig Sinn macht zwei fast äquivalente Varianten anzubieten. Das ist aber auch kein Beinbruch, solange die entsprechende Komponente auf beide Werte prüft und im Zweifelsfall ein Fehler/eine Ausnahme wirft. Dann kommt aber neue Nachteil
- Speicher. Ein (kurzer) String ist zwar nicht die Welt, aber die Masse machts
- Stringvergleich ist aufwendig
Dies ist nur ein kleiner Ausschnitt aus dem Thema “Aussagekräftiger Code”. Hinzu kommt, wie man wo Methoden “sortiert” und wie man sie benennt, ob man einmalige Methodenaufrufe oder Objekterstellung direkt dort verwendet, wo man sie braucht, oder doch nochmal in einer sprechenden Variable zwischen speichert, Klassenbeeźeichner und noch bestimmt ganz viel mehr
Ich kann mir vorstellen, dass man darüber ein Buch schreiben kann, was ich aber nicht tun werde
Das verleitet mich fast zu den Spruch: “Code [Code, nicht das Programm
] ist perfekt, wenn es sich wie ein Buch lesen lässt!”. Ähnlich wie “das perfekte Programm” ist das wahrscheinlich auch unerreichbar, aber eine gute Näherung ist wünschenswert
Das erspart vielleicht viele unnötige Blicke in Anleitungen und API-Docs
Mit diesen Worten verabschiede ich mich für heute. Bald gibt es von mir (hoffentlich) eine neue PHP-Spielerei, aber ich verspreche lieber nichts, sowas breche ich zu häufig
Gute Nacht, KingCrunch
Ich versuch auch oft mit sprechenden Konstanten zu arbeiten, seien es bools, ints oder strings, leider werden, gerade wenn die Klassennamen so wie beim ZF sich aus der Ordnerstruktur zusammensetzen und man da mal in der 3ten, 4ten Ebene ist, die Namen der Klassen schon recht lang und leidet Lesbarkeit und Übersicht wieder etwas.
Ja, das stimmt allerdings, aber das sollte mit Namespaces in 5.3 dann hoffentlich ebenfalls geklärt sein.
Bis dahin eine kleine Überlegung: Klassen selbst sind im globalen Namensraum verfügbar, Klassenkonstanten somit ebenso. Da spricht nichts dagegen die alten globalen Konstanten wieder zu verwenden. Man kann sie (solange es keine Namespaces gibt
) ebenso mit den Namensprefix belegen, den man auch für Klassen verwendet, so das kein Namenskonflikt auftritt.