Ansicht umschalten
Avatar von die kleine Himbeere
  • die kleine Himbeere

mehr als 1000 Beiträge seit 25.10.2012

Exeception-Typen sind völlig überbewertet

Meiner Meinung nach kann man Exceptions auch komplett abschaffen und gegen Errors ersetzen.

Und ein Error sollte auch nicht unterschieden werden, sondern einfach seine Meldung (soweit er überhaupt eine setzt) angezeigt oder ignoriert.

Das Hauptproblem das ich mit Exceptions habe ist dass sie normalerweise *teuer* sind.

Egal ob man sie für Errors oder für andere Zwecke nutzt - sie Kosten Performance.

Der Unterschied ist nur: Errors sind etwas das im Normalfall ohnehin nicht auftreten sollte. Bzw. nur selten. Daher ist es OK wenn sie teuer sind - es tritt ja nicht häufig auf.

Anders ist es hingegen, wenn man Exceptions nicht für Errors verwendet sondern z. B. zur Kontrollfluss-Steuerung - als ein nonlokales "return", um aus geschachtelten Schleifen heraus zu springen (in Sprachen die kein "break" haben) etc.

Dann wird die Exception zu einem normalen Teil des Kontrollflusses, und dann ist es sehr ärgerlich dass sie so (relativ) teuer ist.

Fazit: Es wäre daher besser, wenn die Sprachen anstatt Exceptions für so etwas zu benutzen statt dessen direkte Möglichkeiten bieten um die Kontrollfluss zu verändern: "break", "continue" (am besten mit Label) oder zur Not auch das gute alte "goto".

Und ja, ich sehe auch nach wie vor keine Schande darin "goto" in C zu verwenden um aus mehreren Ebenen einer Schleife heraus zu springen.

Noch besser wäre natürlich ein "continue" oder "break" mit Label, aber da es dies in C nicht gibt sehe ich keinen Grund nicht "goto" zu diesem Zweck zu verwenden.

Manche Leute sind der Meinung dass goto "giftig" sei und "unbedingt" vermieden werden müsste. Diese Leute schreiben lieber in jeder der Schleifenbedingungen eine 2. Abbruchbedingung hinzu die bei jedem einzelnen Schleifendurchgang sinnlos geprüft wird, obwohl ein "goto" ohne solche Performance-Einbußen genau dasselbe erreicht.

Wie auch immer: Exceptions als Ersatz für break oder continue zu verwenden erscheint mir zu teuer.

Bestenfalls als non-lokales "return" könnte es noch einen gewissen Nutzen haben - also wie ein longjmp() in C.

Nur: Bevor ich diesen Fall ernst nehme, will ich erst einmal Einsatzmöglichkeiten dafür finden, die sich nicht trivial auch auf andere Weise implementieren lassen, so dass man dieses Feature tatsächlich braucht!

Was longjmp() angeht, habe ich das eigentlich immer nur in 2 Konstellationen gesehen: Zur Fehlerbehandlung und um aus Signal-Handlern wieder zurück ins Hauptprogramm zu springen.

Der erste Fall wäre immer noch möglich wenn man nur Errors anstatt Exceptions zur Verfügung hätte.

Und der zweite Fall ist so C-spezifisch und low-levelig, dass man so etwas in kaum je in einem normalen Programm braucht.

Jedenfalls ein so exotisches Einsatz-Szenario, dass man das falls nötig mit speziellen Library-Funktionen abdecken könnte, ohne dazu die Sprache mit solch einem Feature zum allgemeinen Gebrauch auszustatten.

Daher - was bliebt übrig?

Exceptions sind genau so wie Exception-Typen unnötig. Sie machen nur alles umständlicher und erhöhen die Bürokratie im Programm, ohne relevantes beizusteuern.

Was man hingegen sehr wohl braucht, sind Errors. Diese sollte man sowohl mit als auch ohne Fehlermeldungen aufwerfen können - und aus.

Ich sehe keinen Sinn darin, allgemein zwischen verschiedenen Arten von Fehlern unterscheiden zu wollen: Es hat nicht funktioniert. Eine Fehlermeldung kann dem Benutzer angezeigt werden. Sonst noch was? Nein! Das reicht in 99,9 % aller Fehler völlig aus.

Was nicht bedeuten soll, dass es die 0,1 % nicht gäbe.

Wenn man etwa auf eine Pipe schreibt und es kommt ein EPIPE-Error beim Schreiben zurück - das ist harmlos. Oder auch EINTR oder EAGAIN. Die sollte man in der Tat sonderbehandeln und nicht gleich eine Fehlermeldung deswegen schieben.

Aber: Das sind auch Low-Level-Funktionen. Wenn man mit read() und write() direkt arbeitet gibt es ohnehin noch keine Exceptions. Man kann diese Sonderfälle daher noch ganz konventionell prüfen und innerhalb der Funktion behandeln.

Und erst wenn ein *anderer* Error daher kommt, erzeugt man aus dem mit strerror() eine Fehlermeldung und wirft sie auf. Dann braucht die aber auch niemand mehr speziell zu unterscheiden. Sondern man kann sie wie jede andere Fehlermeldung behandeln - generisch. Immer auf dieselbe Weise: Fehlermeldung anzeigen, und Programm oder auch nur den aufgerufenen Menüpunkt/Button-Event abbrechen.

Speziell wenn ich mir ansehe was JAVA für einen Aufwand treibt mit seinen Exception-Hierarchien, denke ich mir dabei immer was das für ein sinnloser Aufwand ist.

Wenn eine Exception überhaupt keinen Typ hätte sondern einfach nur einen einzigen selben Typ hätte der eine optionale Fehlermeldung enthält und sonst nichts - das würde fast immer genau dasselbe Ziel erreichen, nur mit viel weniger Bürokratie.

Entsprechend empfehle ich jenen welche in Kotlin nach dem Abfragen verschiedener Exception-Typen zugleich sehnen, darüber nachzudenken ob es nicht zielführender wäre das Abschaffen aller Exception-Typen (außer einem einzelnen, standardisierten) zu fordern.

Dann gäbe es keinen Grund mehr um verschiedene Typen abfangen zu wollen, und das Leben wäre viel einfacher.

In den wenigen Fällen wo es einen Sinn macht verschiedene Arten von Fehlern zu unterscheiden, würde ich vorschlagen für solche Fälle eine 2. Version der entsprechenden Funktion zur Verfügung zu stellen welche direkt Fehlercodes zurück liefert anstatt eine Exception zu werden.

Oder man belässt es bei einer Funktion, gibt ihr aber zusätzliche optionale Argumente mit, durch welche die Funktion im Fall bestimmter Fehler trotzdem normal zurück kehrt anstatt eine Exception zu werfen und der Aufrufer kann das dann prüfen.

Also sozusagen der umgekehrte Weg: Anstatt dass die Funktion irgendwelche Exceptions wirft und man fängt bestimmte davon ab, teilt man der Funktion mit an welchen Exceptions man interessiert wäre und die Funktion gibt diese dann direkt zurück anstatt eine Exception zu werden.

Meiner bisherigen Beobachtung nach gibt es nur ganz wenige Exceptions die von Programmen tatsächlich gezielt abgefangen werden, meist beim Öffnen von Dateien und beim Allozieren von Speicher - ausreichend wenige, dass man die paar Funktionen wie oben vorgeschlagen erweitern könnte, so dass sie Fehlerbedingungen direkt zurück geben anstatt Errors aufzuwerfen.

Eine andere Variante ist es, einfach eine Low-Level Funktion anzubieten die immer Return-Codes zurück gibt und niemals Errors aufwirft, und eine High-Level-Funktion die immer Errors aufwirft.

In dem meisten Fällen wird man dann die High-Level-Funktion aufrufen und sich um nichts mehr weiter kümmern müssen.

Und in den restlichen 0,1 % ruft man eben die Low-Level Funktion selber auf, prüft die "errno"-Werte die einen interessieren, und wirft falls keiner davon auftritt selber einen Error auf falls es einen gab.

Es gibt immer eine Möglichkeit so etwas zu implementieren, und ohne dass man non-Error-Exceptions und Exception-Typen dafür benötigen würde.

Daher plädiere ich dafür: Vereinfacht das Behandeln von Fehlern! Schafft (unterschiedliche) Exception-Typen grundsätzlich ab! Man braucht nur Error-Exceptions wirklich, die anderen Fälle kann (und sollte) man anders abdecken, etwa mittels "break" mit Label.

Und was braucht so ein vereinheitlichtes Error-Objekt?

Eigentlich nur eine optionale Fehlermeldung. Wenn diese fehlt, wird der Fehler als "Interner Fehler" oder so ähnlich tituliert. Man verwendet das in Fällen wo niemand damit rechnet das jemals ein Fehler auftreten wird. Etwa beim Schließen einer Datei die nur zum Lesen geöffnet war. Fälle die so selten auftreten werden, dass es den Aufwand nicht wert ist dafür eine Fehlermeldung vorzusehen (die das Programm größer macht und zudem eventuell auch noch in mehrere Sprachen übersetzt werden muss - völlig sinnfreier Aufwand).

Dann kann man - optional - noch eine Fehler-ID vorsehen - am besten als String, weil kein einzelnes numerisches Format allen Ansprüchen gerecht würde. Man kann dann im Falle eines Error-Logs gezielt nach Einträgen mit bestimmten IDs suchen. Etwa der Fehler-ID "404" im Falle eines Webservers.

Und schließlich noch - eben so optional eine Severity. Damit man beim Loggen die Ausgabe nur auf besonders wichtige Ereignisse filtern kann. Am besten gibt man das als Prozentwert von 0 (total uninteressant) bis 100 (atemberaubend wichtig) an. Und zwar als Ganzzahl. Normalerweise reichen sogar schon 8 oder 16 Stufen völlig aus, aber ein bisschen Granulatität mehr kann zumindest nicht schaden.

In gewisser Weiser erlaubt die optionale Fehler-ID bei diesem System ohnehin bereits das Abfragen bestimmter Fehler, wenn man das denn unbedingt möchte.

Aber das geht dann auch mit einem ganz simplen Vergleich der Error-ID ohne dass man dazu Exception-Typen bräuchte.

Wenn man unbedingt Hierarchien dabei haben möchte, kann man diese Strukturierung direkt in der Error-ID einbauen. Entweder als Bitfelder in einer Fehlernummer, oder als strukturierte Komponenten-Schreibweise mit "." oder dergleichen.

Überdies verbietet einem ja niemand, neben der Fehlermeldung auch einen Stack-Trace wie in Python anzuzeigen, der einen auch genau verrät wo der Fehler aufgeworfen wurde.

Jedenfalls - das alles ist immer noch machbar, auch ganz ohne Exception Typen.

In den meisten Fällen braucht man aber nichts davon, und einfach nur das Aufwerfen eines Fehlers mit (oder auch ohne) einer Fehlermeldung ist völlig genug.

Bewerten
- +
Ansicht umschalten