Sven Rautenberg: Konfiguration, OOP² oder doch nur Rad² ?

Beitrag lesen

Moin!

Du kannst es nennen wie du willst, aber auch in deinem System gibt es Defaultwerte. Wenn du 10 mal den Aufruf new cCookie(14) in deinem System hast, dann mutiert die 14 zu einem Defaultwert. Bloß musst du eben 10 stellen in deinem System ändern, um aus den 14 ein 21 zu machen.

Es ist ein Unterschied, ob ich bewußt den aus der Konfiguration heraus verfügbaren Wert für die überall anders ebenfalls verwendeten 14 Tage verwende, oder ob die Klasse aus obskuren (d.h. von außen unsichtbaren) Quellen auf diesen Wert kommt.

Ich bin gegen solche Default-Magie aus den dargelegten Gründen. Sinnvolle Defaults zu verwenden, um dem Programmierer das Leben leichter zu machen, hat seinen Anwendungsfall dann, wenn man für eine größere Öffentlichkeit Frameworks programmiert. Von solch einem Fall war hier bislang nicht die Rede.

Der Standardfall für Programmieraufgaben ist die Lösung eines konkreten Problems. Das Verwenden der Cookie-Klasse hat also einen konkreten Anwendungsfall mit einer konkret definierbaren erwünschten Lebensdauer. Diese Lebensdauer ist von einem konkreten Konfigurationswert abrufbar und kann direkt in die Klasse hineingegeben werden, ohne Magie. Der Programmierer muss sich an diesem Punkt dann in der Tat die Frage stellen, welche Lebensdauer für das Cookie vorgesehen sein soll. Diese Frage wird entweder reflexartig mit "Die Lebensdauer, die für alle Cookies gilt" beantwortet werden können, und deshalb automatisch den Aufruf der zugehörigen Konfigurationsfunktion hervorbringen, oder es ist ein abweichender Wert mit den daran hängenden Konsequenzen, nämlich der Schaffung eines neuen Konfigurationswertes.

Außerdem ist dieser Aufruf new cCookie(14) für mich eine simple Objekt Initialisierung mit Parameter Übergabe.

Alles, was das korrekte Funktionieren (im Sinne der Aufgabenstellung) eines Objektes beeinflusst, aber potentiell flexibel und dynamisch sein muss, definiere ich als Abhängigkeit (Dependency) des Objekts. Im Sinne des Prinzips der "Inversion of Control" gehört diese Dependency dem Objekt von außen übergeben, anstatt dass sich das Objekt diese Dependency selbst besorgt.

Insofern betrachte ich auch simple Konfigurationsparameter als Dependency, die deshalb folgerichtig zu injecten sind. :)

Eine Dependency Injection muss für mich aus einem gesonderten Bereich bestehen, der sich primär um den Controllfluss kümmert. Also sowas:

function di()
{
   newcObject( new cOtherObject() );
}

  
Täte es sehr weh, wenn du in genau so einem Bereich folgende Funktion notiertest:  
  
~~~php
function getCookieClass()  
{  
    return new cCookie($this->configValue("CookieLifeTime"));  
}

Generell stell ich mir die Frage wieso einfachste Dinge mit irre komplizierten Namen versehen werden.

Ich würde dir ja sehr gerne zeigen, wohin es führt, wenn man glaubt, dass man es mit einfachsten Dingen zu tun hat und deshalb die direkteste Programmiermethode verwendet, leider sollte ich wohl allzu direkte Veröffentlichungen lieber nicht tätigen.

Deshalb musst du mir leider glauben, dass Projekte, sofern sie eine gewisse Größe und Komplexität überschreiten, auf diese Weise immer aufwendiger zu warten sind, bis am Ende sich keiner mehr traut, auch nur irgendwas zu verändern, und man solch ein Knäuel von Code im Prinzip nur noch wegwerfen kann.

Es kann ja durchaus sein, dass dein Projekt so klein und übersichtlich ist, dass dein Ansatz funktioniert. Ich bestreite nicht, dass es funktioniert, weder grundsätzlich, noch konkret beim Ausführen des Codes an sich. Ich habe nur so meine Erfahrungen gesammelt im Laufe der Zeit, welche Ansätze auf Dauer nicht funktionieren, und welche besser sind. Und meine persönlichen Meinungen stimmen mit der restlichen Fachwelt erstaunlich gut überein.

Das Problem dabei ist, dass viele meinen Sie haben Ahnung nur weil sie ein paar englische Fachwörter kennen. Ich hab z.B. keine Ahnung was es so alles in einem Auto gibt oder gar wie es funktioniert. Trotzdem kann ich Autofahren.

Du redest gerade mit einem Automechatroniker - um im Bild zu bleiben.

Das gleiche gilt für die Benennung von Methoden. Nur weil in der Methode für die Lebensdauer kein "Default" vorkommt heißt das noch lange nicht, dass das eine Wert fürs Kuchenbacken ist.

Über die Wahl von Variablen- und Methodennamen kannst du beim Leser deines Codes recht gut steuern, ob er den Code versteht, oder nicht.

Nur mal ein Beispiel:

function checkValidation($authdata1, $authdata2)  
{  
    if (empty($authdata1) || empty($authdata2)) {  
        return 14;  
    }  
    if (strlen($authdata1) != 5) {  
        return 23;  
    }  
    if (preg_match("/\\./", $authdata2)) {  
        $data = explode(".", $authdata2);  
        //...  
}

Anhand der Variablennamen kannst du in diesem Stückchen Code nicht erkennen, warum auf Stringlänge 5 oder auf den Punkt getestet wird.

Und nun ersetze mal $authdata1 durch "$postleitzahl" und $authdata2 durch "$geburtsdatum".

Wir dürfen nicht den gleichen Fehler machen wie die Politik und die Sprache Zweckentfremden. Sprache ist zum transportieren von Informationen da und nicht zum täuschen.

Programmierer machen aber leider oft den Fehler, durch falsche Sprache andere Programmierer und vor allem auch sich selbst zu verwirren.

Das nach außen nicht direkt der Defaultwert ersichtlich wird stimmt. Da hast du auf jeden Fall recht (und ehrlich gesagt nervt mich das selber, auch die Tatsache das man in php über ein Interface nicht den Typ des Rückgabeparameter bestimmen kann). Du behebst diesen Markel in dem du 10 Aufrufe mit dem Übergabewert 14 hast und somit 10 Stellen an denen du eventuell etwas ändern musst.

Ich habe 10 Stellen, an denen ich den in der Konfiguration einzigen Cookie-Lifetime-Wert explizit in die Klasse übergebe und damit dokumentiere, welche Konfigurationsparameter an diesem Punkt in dem nutzenden Code aktiv werden. Alternativ nutze ich explizit einen anderen, für den speziellen Zweck geschaffenen Konfigurationsparameter. Das System verlangt vom Programmierer immer, dass er sich Gedanken machen muss über den an dieser Stelle korrekt zu verwendenden Konfigurationsparameter, weil er andernfalls an dieser Stelle nicht weiterprogrammieren kann, weil die Klasse dann nicht läuft.

Das bedeutet auch, dass an dieser Stelle die Aufgabe, konkret jetzt also das Cookiesetzen, vollständig verstanden wurde, und deshalb diese Entscheidung vom Programmierer getroffen werden kann. Eine einstweilen nur mit Defaults befüllte Klasse, Hauptsache sie funktioniert irgendwie, hat in meinen Augen das Potential für sehr gut versteckte Bugs (und ich gebe gern zu, dass es bei einer Cookie-Lebensdauer extrem schwer wäre, das wirklich argumentativ zu belegen), oder für die wachsende Angst der Programmierer, dass beim Ändern dieser konfigurierten Default-Werte tatsächlich alles wir gewünscht anders, aber immer noch korrekt funktioniert. Stattdessen wird man vermutlich irgendwann dazu übergehen, den Default-Werten in der Klasse zu misstrauen und immer explizit eigene Werte anzugeben, damit wenigstens dieser Codeteil beim Ändern des Defaultwerts nicht kaputt geht.

Ich setze dieses Wissen voraus oder muss es dokumentieren (wie dedlfix es bereits erwähnte). Ersteres bricht die Regeln der OOP. Gut ich würde jetzt sagen bei einem Default wert macht das nichts. Dann kannst du aber sagen dass ein System nicht nur einen Default wert hat und damit hättest du wiederrecht. Und schwups sind wir beim Problem der ersten Frage angelangt und bei meiner Neugier. Ich akzeptiere diesen Markel, dass man eben gegen OOP verstößt. Dafür zentralisiere ich Defaultwerte in einem Singleton und erhoffe mir dadurch Arbeitserleichterung. Und deshalb habe ich mich hier überhaupt zu Wort gemeldet. Dein Ansatz hat mich sehr interessiert, doch konntest du mich bis jetzt nicht überzeugen.

Ich weiß. Die Frage an dieser Stelle: Schreibst du Unit-Tests mit PHPUnit? Und schreibst du die VOR oder NACH der Erstellung deiner Klasse?

Wenn du keine Unit-Tests schreibst, und auch noch nicht erfahren hast, auf welche wunderbare Weise dir Unit-Tests den Arsch retten können, weil testbarer und getesteter Code einfach eine viel höhere Softwarequalität liefert, so dass man tatsächlich termingerecht liefern kann, anstatt in einer endlosen QA-Phase immer neue Fehler zu finden, dann verstehe ich deine Einstellung vollkommen.

Und vermutlich wirst du extrem fluchen, wenn du in dem jetzigen Zustand auch nur den ersten Unit-Test schreiben solltest, weil du deine Klassen nicht isoliert testen kannst, weil sie nicht isolierbar sind, sondern z.B. durch das Konfigurations-Singleton immer zwingend auf genau dieses zugreifen.

Und nein Objekte haben keine zwei Gesichter sondern x. Wenn du nur eine Eigenschaft hast, welche die Werte zwischen 0 - 99 annehmen kann, hast du schon 100 unterschiedliche mögliche Objekte.

So ein Objekt hätte für mich vier oder fünf Testfälle: -1, 0, 99 und 100, vielleicht noch was aus der Mitte: 42.

Es hätte aber trotzdem nur ein Gesicht. Nämlich das mit 100% Code-Coverage durch Tests. ;)

- Sven Rautenberg