Facebook
Twitter
Google+
Kommentare
25

Wann verwende ich Exceptions?

Diesen Artikel schiebe ich schon eine ganze Weile vor mir her. Obwohl es für mich intuitiv klar ist, wann ich Exceptions zu nutzen habe, finde ich es auf keinen Fall einfach dies auch in Worte zu fassen. Heute will ich es einfach mal versuchen, wenn es nicht klappt, dann hab ich ja nichts verloren. 

Ich versuche mich mal mit der Standard Definition. Excpetions (Ausnahmen) werden immer dann geworfen, wenn etwas unerwartetes passiert. So das war’s, ich bin raus. Na gut, vielleicht noch mal ein paar Beispiele, was etwas unerwartetes sein könnte. Das Öffnen einer Config Datei, die ich für den Programmablauf brauche, so etwas ist unerwartet. Der Versuch des Öffnens einer Datei, deren Name mir der User als String übergeben hat, ist keine Ausnahme wert, falls diese nicht vorhanden ist. Niemals dem User vertrauen, ist eh so ein Leitspruch, der einem viel Ärger ersparen kann.

Ein Fall, den ich vor kurzem gesehen habe (ich weiss aber leider nicht mehr wo) ist das Validieren der Userinformationen beim Anlegen eines neuen Benutzers. Hier gab es so etwas wie setUserName( $name ). Diese Methode hat eine Exception geworfen, falls der Name nicht einem bestimmten regulären Ausdruck genügt hat. Bis jetzt passt das wunderbar zu meiner Definition. Jetzt ging es aber weiter. Die Eingaben des Users wurden direkt in diese Methode gepumpt. Das darf natürlich nicht sein, denn wir wissen ja, Userinput ist böse. Wir brauchen also so etwas wie isValidUsername( $name ), was ich vor dem zuordnen aufrufen sollte. Und schwupps wird keine Ausnahme mehr verwendet, ausser der Name ändert sich irgendwie auf unerklärliche Art und Weise zwischendrin und das wäre wirklich unerwartet.

Ich glaube jetzt sind wir mal gar nicht schlauer. Was ich eigentlich sagen will, Exception darf man gerne werfen, man muss dem Programmierer nur die Möglichkeit drum herum zu kommen. Der Fokus sollte hier auf der Validierungsfunktion liegen. Erst diese macht das ganze anwendbar, ohne in eine „falsche“ Ausnahme zu laufen.

Aber warum sollte man Execptions nicht als normale Kontrollstruktur verwenden, wie zum Beispiel IF? Von Java weiß ich, dass Exceptions sehr kostspielig sind, also kostspielig im Sinne von dauern sehr lange. Ob man dies eins zu eins auf PHP übertragen kann bin ich mir nicht sicher. Aber vielleicht liest ja ein Johannes mit und der kann seinen Senf dazu abgeben. Was ich aber weiß, ist dass Exceptions, die nicht richtig gefangen werden und irgendwo unerwartet im Source dann doch gestoppt werden die Wartbarkeit und die Fehlerfindung so extrem erschweren, dass man keinen Spaß man daran hat.

Ich glaube aber, ihr macht es was ihr wollt und lasst euch von mir nicht reinreden. Macht also weiter so, wird schon richtig sein. Ich muss ja euren Code nicht warten.

Ü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

25 Comments

  1. Ich persönlich logge mir immer alle geworfenen Exceptions auf Entwicklungssystemen &mdsash; Ich leite von mir geworfene allerdings auch von einer eigenen Exception-Klasse ab. Denn das Problem, dass Exceptions als Alternative zu Booleschen Rückgaben verwendet werden kann(!) in der Tat das Debugging entscheidend erschweren.

    Reply
  2. Ich versuche es ähnlich wie Nils und fahre mehrgleisig, schaffe es aber nicht immer. Beispiel:

    Userinput wird generell erstmal direkt nach der Annahme durch das Script geprüft. Dank des Zend Frameworks geht das wunderbar einfach mit Hilfe von Validators. Damit hat man schonmal die gröbsten Dinge abgefangen (Zahlenfelder nur Zahlen, mit Regex auf gültige Zeichen überprüfen etc).

    Zusätzlich habe ich in den eigentlichen Klassen (also beispielsweise kurz vor dem Speichern in eine Datenbank) nochmal eine Prüfung, ob ein dort übergebener Wert auch wirklich so in die Datenbank rein darf. Eigentlich sollte man meinen, dass die „erste Schicht“ (Formular-Validators) ja reichen müßte, doppelt machen ist immer doof. ABER: Wir haben hier viele weitere „Eingänge“ wie zB kleine Scripte, SOAP, RPC, AMF usw, die direkt mit den Klassen arbeiten, und an den Formular-Validators der Webseite vorbeiarbeiten.

    Naja, nicht ideal, aber besser einmal zuviel prüfen als einmal zu wenig. Nichts ist ärgerlicher als verlorene Daten oder Inkonsistenzen, gerade auf professioneller Ebene. Mein kleines Heimblog würde ich wahrscheinlich nicht doppelt und dreifach absichern.

    Reply
  3. „Diesen Artikel schiebe ich schon eine ganze Weile vor mir her.“

    Verständlich! Leider trägt dieser Artikel auch nichts zum Verständnis bei. Das Aussagekräftigste steht im 2. Satz im bsatz 2, Satz 2.

    Schade, da hätte ich mir klare Worte gewünscht und ein paar gute Beispiele.

    Einige Fragezeichen tauchten bei folgenden Passagen auf:

    „Das Öffnen einer Config Datei, die ich für den Programmablauf brauche, so etwas ist unerwartet.“

    „… Macht also weiter so, wird schon richtig sein.“. Warum dann der Artikel???

    Bitte nicht in den falschen Hals bekommen, ein wenig Kritik ist, im Hinblilck auf Besserung, sicherlich erlaubt.

    Viele Grüße,
    Thorsten

    Reply
  4. Ich finde Exceptions zur Validierung perfekt, natürlich nicht im DAO-Objekt sondern in einem extra Validation-Klasse für das DAO-Objekt.

    Was nützt mir ein Boolean der mir nur sagt falsche Eingabe?
    In einer Exception kann ich eine Liste anlegen in der jedes einzelne Attribut mit den einzelnen Fehlern beschrieben wird.
    Missbrauche ich die Exception?
    Ich weiß nicht ob das so 100%ig sauber ist, leider konnte ich bisher mit keinem darüber ernsthaft diskutieren.
    Was meint ihr?

    Ehrlich gesagt, ich hasse diese Boolean gewrickel.

    Reply
  5. @Christoph: Vielleicht definierst du dir einfach ein ResultObject, dass genau wie deine Exception deine Fehler beinhaltet? Zusätzlich hat es natürlich eine isValid( ) Methode. Du würdest also immer ein Result zurückbekommen und es wäre auch konsitent vom Typ her. Denn eigentlich nutzt du ja gar nicht die Element, die eine Exception so besonders macht.

    @Saphir2k: Klar ist Krirtik erlaubt und ich war mir vor dem Schreiben schon sicher, dass es nicht so einfach wird. Und wenn es diesmal nicht ganz so geklappt hat, dann kann ich diesmal damit leben. Aber wie immer bin ich über jede Idee dankbar und wenn du Lust hast, kannst du gerne mal einen Gastbeitrag schreiben.

    Reply
  6. Ich nutze Exceptions in internen Modulen. Diese implementieren zum Beispiel den Zugriff auf einen Webservice, übernehmen irgendwelche Datentransformationen, parsen/rendern Daten, etc. Diese Module werden an verschiedenen Stellen verwendet, befinden sich aber in einem Web- oder CLI-Kontext. Hier finde ich die Ausnahmen sehr angenehm, um solche Konstrukte wie $resultObject->isValid() vermeiden zu können. Außerdem kann ich damit super cool Methodchaining betreiben:

    $foo=$this->getWebservice()
    ->configure($configs)
    ->request()
    ->getResponseBody();

    Reply
  7. Eine spontane Idee: (ich hab den Gedanken nicht fertig gedacht – vielleicht ist es auch Bullshit)
    Eine exceptionfreie Alternative zu boolschen Überprüfungen wäre ein State Objekt (Singleton? Vielleicht sogar im Zusammenhang mit AOP): In jeder relevanten Methode wird überprüft, ob der Status noch OK ist. Wenn nicht, wird entsprechend gehandelt. Wäre das was?

    Reply
  8. @Rudwig: Ich find’s toll, wenn man solche Ideen hat, aber ich glaube diesmal wird sie kein Erfolg. Was würde ich denn machen, wenn der Status nicht mehr ok ist? Ich habe doch keine Ahnung, an welcher Stelle denn nun der switch zwischen Ok und Kaputt passiert ist. Vielleicht hab ich’s auch einfach nicht verstanden 🙂

    Reply
  9. Man müßte natürlich zusätzliche Informationen in diesem State Object speichern, wie Message, Code, Stack etc. Genau das, was eine Exception macht 😉
    Problem dabei wäre, dass man nach jedem Funktionsaufruf dieses State Object überprüfen müßte. Dann kann man auch gleich Exceptions nutzen…
    Wenn man sich eine gute Struktur aufbaut, spart man durch Exceptions auch viel Arbeit, je weiter „oben“ man sie catched. Und wenn man will, kann man sie auch sehr feinkörnig abfangen, und quasi um jeden Funktionsaufruf einen try-catch-Block machen. Häufig ist das aber nicht gewollt.

    Ich bin ganz zufrieden mit Exceptions, und sehe keinen Grund, an dem Verfahren was zu ändern. Wenn ich da an Zeiten zurückdenke, wo es das nicht gab. Im PEAR-Framework kann man das noch gut beobachten: Nach fast jedem Funktionsaufruf muß man den Rückgabewert überprüfen:
    if (PEAR::isError($ret))…
    Ein absoluter Krampf, mir wird grad schlecht… Ohje…

    Reply
  10. Ich nutze auch gerne das von Java/C# übernommene Konzept von inneren Ausnahmen, also in einem Modul gefangene Ausnahmen weiter zu werfen:

    try {
    $this->doStuff();
    }
    catch(MyException $ex) {
    throw new FooException(‚Failed to do stuff‘, $ex);
    }

    Nur dran denken, auch die __toString-Methode der Ausnahme zu erweitern, um beide Stacktraces anzuzeigen.

    Reply
  11. Hm. Ich glaube ich wurde wirklich nicht verstanden. Nicht mal von mir! 😉
    Ich hab schon sowas wie „PEAR::isError($ret)“ gemeint, nur das es sich beim State nicht um einen Error/eine Exception handelt, sondern um eine erwartete Ausnahme 🙂 jedoch für Fälle, die nicht mehr mit einer Validierung abgedeckt werden sollen/können.
    (@Michael: Sowas ist mit AOP übrigens nicht mehr ganz so ein großer Krampf.)
    Ich versuchs mal mit einem Beispiel:
    if ($state->isBadUser()) {
    //send him a lovely message
    }
    Falls der Ansatz immer noch nicht verstanden ist, werd ich mal damit rumexperimentieren und dann drüber bloggen.

    Reply
  12. Ein super Thema!

    Leider hat da jeder Entwickler seine Vorlieben und die Exceptions verkommen oft zu „GOTO“-Krücken – das Ergebnis: Spaghetti-Code.

    Ich stehe da voll hinter Nils und sage: Pflegt das selber!

    Exceptions sind NUR für Ausnahmen vorgesehen – habt ihr den Programmfluß zu steuern erinnert euch an die Coretools dafür.

    Reply
  13. Ok, jetzt habe ich diesen Artikel den ganzen Tag offen und da angesprochen wollte ich auch antworten, wollte davor aber noch recherchieren, wozu ich aber heute nichtmehr komme.

    Zur Frage nach der Performance: Das habe ich nie genauer durchgemessen. Was auch daran liegt, dass ich solche Mikro-Benchmarks in 99% der Fälle blöd finde schönerer Code kostet (fast) immer nur ist das Bottleneck halt doch genau eine Datenbank Query. Sei es drum was is meine Schätzung: Von der Implementierung her, stark vereinfacht, funktionieren Exceptions so, das nach einem Statement geschaut wird ob das „Exception Flag“ gesetzt ist (das is kein Flag sondern nen Pointer auf das aktive Exception-Objekt oder NULL) oder nicht. Ist es nicht gesetzt geht es weiter, falls es gesetzt ist passiert ein Sprung zum Ende des aktuellen Frames oder try{}-Blockes. Dort wird geschaut ob es ein catch gibt der passt – je mehr catch Blöcke und desto mehr inheritance desto teurer – wobei Fehlerfall Performance-Technisch egal sein sollte, sonst hat man eh ein Problem mit der Architektur – wenn kein catch Block passt aufräumen und weiter nach oben. Wie schaut das ohne Exceptions aus? Das Exception-Flag muss trotzdem geprüft werden, danach muss mit userland Code der Fehlerzustand geprüft werden und nach oben durchgereicht werden. Nun is userspace code langsamer als das direkt in der Engen zu machen weswegen ich vermuten würde (wie gesagt nicht durchgemessen …) das das oft schneller ist, zumindest so lange die Exception nicht auftritt.

    Das Problem das ich mit Exceptions sehe ist, dass schnell „wild“ irgendwas passiert – ich rufe irgendeine Library-Funktion, die ruft wieder etwas auf und in irgendeinem Spezialfall wirft das ne Exception. Die Code coverage meiner Tests von meinem Code ist 100% und die Library hat auch 100% Abdeckung aber den Edge-Case finde ich erst auf Produktion wodurch ich dann nen Fatal Error habe und der User nur ne weiße Seite bekommt (display_errors -> 0, natürlich auf Produktion) Das macht Wartung recht aufwendig. Java versucht das zu lösen in dem man Exceptions die durchgereicht werden in der Signatur angeben muss (int foo() throws MyException {}) was bei meinen Java-Dingen aber dennoch noch genug „Uncaught Exceptions“ geliefert hat, somit also auch nicht die perfekte Lösung ist…

    So, muss aber mal nen Release anouncement schreiben 😉

    Reply
  14. @Johannes: Klingt sehr interessant, vielen Dank. Ich bin mir auch gar nicht mehr so sicher, wo ich es damals gelesen habe, dass in Java Exceptions so kostspielig sind. Ich hab irgendwas in Erinnerung, dass man die VM anhalte müßte, um an den Stacktrace ranzukommen. Ich weiss jetzt auch gar nicht, ob meine Aussage Sinn macht 🙂 Kenne die Interna von java nicht wirklich. Aber ich versuche es mal rauszufinden.

    Reply
  15. Ich weiß nicht warum stört mich auch an der php docu aber alle beispiele gehen davon aus das nur ein command im try block steht. warum ?

    Das schöne an exception ist doch das der codeablauf dort unterbrochen wird wo der fehler passierte und dann in den catch block gesprungen wird.

    Mal ein beispiel peudo code um das zu veranschauchlichen:

    try {
    $object = new FetchRowFromDatabase();
    $anderesobject = new DoSomethingWithIt($object);
    $object->WriteToDatabase($anderesobject);
    }catch(Exception $e){
    …. was auch immer zur fehlerbehandlung notwendig ist.
    }

    Jede der obigen klassen throwed eine exception, (oder auch eine eigene, um entsprechend drauf zu reagieren) ich muss mich nicht mehr drum kümmern ob es $object->WriteToDatabase wirklich gibt, wenn die initalisierung von $object eine exception wirft komm ich nicht mehr zu dem punkt.

    Ich sehe den vorteil von Exceptions einfach darin die Fehlerbehandlung zentral in einem oder mehreren catch blöcken zu haben, und dazwischen nur noch um die ablaufsteuerung selbst kümmern zu müssen ohne auch noch mit fehlerbehandlungen zu arbeiten.

    Ludwig

    Reply
  16. Nils, naja, was teuer ist ist eine Exception überall durchzureichen, und ja beim Erzeugen des Exception Objekts muss ein Stacktrace erzeugt werden, Frage ist was die Alternative kostet … und ich hatte bewusst geschrieben, „dass das oft schneller ist, zumindest so lange die Exception nicht auftritt.“ Man beachte den zweiten Halbsatz. Also der Vergleich zwischen „nach jedem Statement auf Fehler prüfen“ und „einen längeren try-Block haben, evtl. sogar über Funktionscalls hinweg“.

    Die Performance bei „Exception tritt auf“ kann stärker leiden, da gilt dann aber wieder „Es ist eine Ausnahme-Situation“ nach der oft doch eh die Terminierung steht (naja, ok, das is meine Meinung – keinerlei cotrol-flow via Exception)

    Reply
  17. Ludwig, grundsätzlich stimme ich dem zu, nur sollte man „catch (Exception $e)“ vermeiden sondern spezialisierte catch-handler nehmen, sonst vergisst man schnell mal ne Exception die besonderes Handling erfordert und bekommt Wartungsschwierigkeiten.

    Reply
  18. @johannes. Ja hab ich eh auch geschreiben „… in einem oder mehreren catch blöcken zu haben …“ wieder ein zusimples beispiel genommen 🙂

    btw. danke für die erklärung zum internen ablauf von exceptions.

    Reply
  19. Bisher habe ich noch nicht so viel mit Exceptions gearbeitet. Aber warum erschweren diese das Debugging? Ich bin bisher davon ausgegangen, dass eine Exception das Debugging erleichtert, weil diese doch um einiges detailierter ist als ein false als Rückgabewert.

    Reply
  20. @Chris

    Du meinst den „Missbrauch“ der Exception als Debbuger einfach temporär in den Code geschrieben – damit hast du Recht.

    „Richtige“ Exceptions bleiben im ProduktivCode und steuern das Verhalten deiner Anwendung bei unerwarteten Fehlern.
    So ist die Definition.
    Wie du an dieser Diskussion siehst, werden sie aber auch zur Ablaufsteuerung benutzt, weil einige nicht ohne GoTo auskommen ;).

    Reply
  21. Während ich viele Beiträge von phm hier schätze und gern lese, muss ich leider sagen, dass ich diesen Artikel für verwirrend, durcheinander und unterm Strich für nicht wertvoll halte.

    Exceptions sind in PHP stiefmütterlich behandelt, da sind wir uns wohl alle einig. Und wenn man kein Framework benutzt, das von Exceptions fleißig Gebrauch macht, kann man natürlich argumentieren, warum man mit Exceptions um sich schmeißen soll, wenn die Core-Funktionen von PHP das selbst nicht tun.

    Reden wir aber von größeren Projekten, in denen man eine saubere und übersichtliche Fehlerfallbehandlung einsetzen möchte und wartbaren Code erzielen möchte, dann sind Exceptions auch für PHP ein gutes Mittel.

    Das Performance-Argument ist m.E. absolut überbewertet im Artikel. Ordentlich strukturierter Code erzielt in aller Regel erheblich mehr Vorteile.

    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