Facebook
Twitter
Google+
Kommentare
9

„Gültigkeit“ von Objekten

Schon wieder ein Programmierbeitrag. Man merkt, dass ich zur Zeit wieder beruflich am Entwickeln bin. Ist auch immer mal wieder schön wirklich Code zu schreiben. Aber wie immer halte ich mich zu lange mit der Einleitung auf, denn ihr wollt wahrscheinlich nur das tatsächliche „Wissen“ aus diesem Beitrag aufsaugen und nichts aus meinem Leben erfahren. Ätsch mir doch egal …

Nein! Wir fangen doch einfach an. Ich bin heute über eine Klasse gestolpert, die an sich nichts böses macht. Kürzen wir das mal runter. Sie hatte zwei öffentliche Methoden execute() und getResult(). Ist ja intuitiv, dass man getResult nicht aufrufen darf, bevor man eine execute durchlaufen hat. Aber irgendwie finde ich das doof. Natürlich ist es in dem Beispiel wirklich intuitiv, so muss es aber nicht immer sein. Ich könnte mir viele Abhängigkeiten von Methoden vorstellen, die man nicht haben will.

Was ich euch also sagen will. Wie schön wäre die Welt, wenn ich mir eine Klasse nehmen kann, sie instanziere und ich danach auf jede Methode, die sie mir zur Verfügung stellt zugreifen kann. Das wäre doch echter Luxus, oder? Zumindest müsste ich dann ein Stück weniger über die Klasse wissen, denn sie gibt mir den Weg vor, den ich gehen muss.

Was müssen wir also machen? Zuerst müssen wir den Konstruktur klug wählen. Alle notwendigen Abhängigkeiten nach außen muss ich von beginn an in dem Objekt haben. In dem Fall würden also setter keine gute Idee sein, außer man hat Standardwerte, dann geht das. Gut, jetzt haben wir alle lebenswichtigen Dinge übergeben. Das erste Problem haben wir also gelöst. Dummerweise nicht das Problem, das wir oben beschrieben haben, aber so ein kleiner Exkurs kann ja nicht schaden.

Nehmen wir also das Beispiel von oben. Wir haben die execute aufgerufen. Wenn wir das jetzt „klug“ machen, dann lassen wir diese Methode ein ResultObjekt zurückgeben, auf welches ich dann die Methoden aufrufen kann, die neu sind. Sowas wie ein wasExecutionSuccessful() oder getErrors(). Hier ist der Fantasie wohl Tor und Tür geöffnet. Was ich also eigentlich sagen will. Überlegt einfach mal,. ob ihr Klassen nicht trennen könnt, falls ihr ansonsten eine unschöne, nicht intuitive Abhängigkeit drinnen habt.

Jap soviel zur Gültigkeit von Objekten und Methoden.

Über den Autor

Nils Langner

Nils Langner ist der Gründer von "the web hates me" und auch der Hauptautor. Im wahren Leben leitet er das Qualitätsmanagementteam im Gruner+Jahr-Digitalbereich und ist somit für Seiten wie stern.de, eltern.de und gala.de aus Qualitätssicht verantwortlich. Nils schreibt seit den Anfängen von phphatesme, welches er ebenfalls gegründet hat, nicht nur für diverse Blogs, sondern auch für Fachmagazine, wie das PHP Magazin, die t3n, die c't oder die iX. Nebenbei ist er noch ein gern gesehener Sprecher auf Konferenzen. Herr Langner schreibt die Texte über sich gerne in der dritten Form.
Kommentare

9 Comments

  1. Sehr schön! Der Artikel und die Aussage. 🙂

    Eine andere, nicht ganz so schöne Möglichkeit ist die Abhängigkeiten zwischen den Methoden in Kombination mit „Lazy Loading“:
    Die getResult() weiß ja, dass vorher die execute() aufgerufen werden soll, also macht sie das einfach. Die execute() muss dann darauf achten, dass sie unter gleichen Umständen nicht zweimal ausgeführt werden muss und gibt das bereits vorhandene Ergebnis zurück, falls es schon existiert.

    Reply
  2. Die Auflösung des execute-getResult-Problems fand ich schon gut, ist aber noch nicht konsequent umgesetzt. getResult war aus puristischer Sicht problematisch, weil es nicht „Tell, don’t ask!“ entspricht. wasExecutionSuccessful() und getErrors() gehen in die gleiche Richtung. Ich hätte besagtes Objekt eher mit Kommandos bzw. Callbacks (ist ja PHP) gefüttert, die im Erfolgs- bzw. Fehlerfall ausgeführt werden.

    Beispiel:
    $obj->execute($command, $onSuccess, $onError);

    Die execute-Methode sähe dann ungefähr so aus:
    public function execute(Command $cmd, SuccessHandler $onSuccess, ErrorHandler $onError){
    // Tries executing command.
    /* … */
    if ($success){
    $onSuccess->handleResult($result);}
    else{
    $onError->handleError($error);}}

    Die Methoden würden nicht unbedingt handleResult und handleError heißen, sondern irgendwie spezifisch, je nach Anwendung, das Gleiche gälte für Command und die handler. handleError könnte dabei auch einfach eine Exception werfen.

    Bonus in diesem Fall: Der Nutzer des Objektes kann eigentlich kaum was falsch machen.

    Eine in der Art wie im Artikel besonders verkorkste API ist m.E. beispielsweise mysqli – klar, ich weiß, nehmen alle ORMs und so, aber machen wir es doch mal spaßeshalber. Wenn man da Prepared Statements nimmt, ist der typische Weg wie folgt ($connection ist die Verbindung):
    $stmt=$connection->prepare($query);
    /* Fehlerbehandlung */
    $stmt->bind_param(/* … */);
    /* Fehlerbehandlung */
    $stmt->execute();
    /* Fehlerbehandlung */
    $stmt->bind_result(/* … */);
    /* Fehlerbehandlung */
    /* Fetchen der Ergebnisse, dabei jedes Mal Fehlerbehandlung */
    Grauenvoll! Abgesehen davon ist es in diesem Fall besonders perfide, weil man durchaus execute() und bind_result() tauschen kann und in den meisten Fällen fliegt es einem nicht um die Ohren.

    Warum ich das alles geschrieben habe? Nun, auch in dem Fall, dass es so gelöst ist, kann der Nutzer der Klasse immer noch was falsch machen. So wie jemand vergessen könnte, dass man vor getResult() execute() aufrufen muss (oder es aus Komplexitätsgründen irgendwie nicht aufgefallen ist), könnte auch jemand vergessen, dass man vor der Verarbeitung des Ergebnisses auf Fehler prüfen muss. Das ist im Prinzip nichts anderes als ein Nacheinander-ausführen-müssen von Methoden.

    Reply
  3. @Nils
    „Ist ja intuitiv, dass man getResult nicht aufrufen darf, bevor man eine execute durchlaufen hat.“

    Ist das Ironie? Ich hoffe doch…

    Genauso wie bei Methoden kann man in deinem Fall auch die Klassen nach Command und Query trennen:

    class ExecuteSomethingWonderful{};
    [class reportSomethingWonderfulHappened{};]

    Je nachdem, was man da „executed“ hat, ist Letztere evtl. sogar überflüssig. Ein Command soll ausgeführt werden, basta.
    Ob das, was Mr. Command da gemacht hat, zu irgendeinem Resultat oder einer Statusänderung geführt hat, ist nicht das Problem des Commands.
    Dafür sind die Berichterstatter bzw. Kontrolleure zuständig (Messaging, Exceptions, Pub/Sub etc. etc.).

    Aber dabei SRP und vor allem CQS vergessen. Aufweichen darf man diese Regeln in trivialen Fällen aber durchaus.

    Reply
  4. Ich bin da mit mario. Nicht alle Abhängigkeiten lassen sich auslösen – ich denke da spontan an Factory-Patterns – und im Hinblick auf die Grundanforderung („Natürlich ist es in dem Beispiel wirklich intuitiv, so muss es aber nicht immer sein. Ich könnte mir viele Abhängigkeiten von Methoden vorstellen, die man nicht haben will.“) sind Callbacks und/oder weitere beteiligte Objekte ja noch viel weniger intuitiv. Exception werfen und ich merke, dass ich was falsch gemacht habe. Keep it simple, stupid.

    Reply
  5. Die Lösung von Timo gefällt mir.
    Keine Methode sollte Abhängigkeiten zu anderen Methoden des Objekt besitzen, da es zum einen fehleranfällig ist und hierfür zudem Wissen über die internen Abläufe eines Objekts benötigt wird, das außerhalb nichts verloren hat.

    Eine Methode sollte zu jeder Zeit immer alle Informationen haben um vernünftig laufen zu können.

    Im mysqli DB-Besipiel wäre es meiner Meinung nach z.B. besser ein Connection Objekt zu haben, welches Statement Objekte ausführen kann.
    $stmt = new Statement( $query );
    $stmt->bindParam(/* … */);
    $connection->execute( $stmt );

    Bei synchronen Abläufen (führe aus [und warte auf Ergebnis]) also wie im mysqli-Beispiel.
    Und für alle asynchronen Abläufe (führe aus und hole das Ergebnis irgendwann/wenn da) eben Callbacks verwenden.

    Halte ich für intuitiv und zugleich ordentlich, weil man eine Sache an genau einer Stelle erledigt und nicht über mehrere Stellen verteilt.

    Reply

Leave a Comment.

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen