David Schmitt / Ein Gutes Exception Beispiel
StartSeite | DavidSchmitt/ | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern
Eine Textsicherung meines Refactorings von EinGutesExceptionBeispiel.
Es wird auf ExceptionsDiskussion behauptet, dass Exceptions viele Vorteile hätten. Mir sind immer nur Nachteile aufgefallen, aber vielleicht fehlt einfach nur EinGutesExceptionBeispiel.
Dateioperationen | |
Für ein gutes Beispiel kann man sich http://java.sun.com/docs/books/tutorial/essential/exceptions/definition.html anschauen. Dort wird recht anschaulich das Einlesen einer Datei mit Returnwerten und Exceptions gegenübergestellt.
Laufzeitfehlerlokalisierung | |
Beispiel von HermannWacker: Lokalisierung von LaufzeitFehlern mit Ada in großen Projekten. Siehe AdaExceptionHandler.
VolkerGlave meint, Exceptions wurden nicht zur Diagnose von Programmierfehlern erfunden. Andere wiederum sehen Programmierfehler genauso als Ausnahmesituationen (siehe ExceptionsDiskussion) die via Exceptions behandelt werden können/sollten.
Einfaches Codebeispiel | |
Hier ein Beispiel von gR, wie einfach Programme mit Exceptions aussehen können, verglichen mit Programmen, die keine Exceptions haben. Beispiele in Java:
Mit Exceptions
| class Hello {
Hello(String aMessage) {
this.message = aMessage;
}
public void sayHello() {
System.out.println(message);
}
private final String message;
}
irgendwo im Quelltext:
Hello hello = new Hello(time.getHours() >= 12 ? "Good afternoon world!" : "Good morning world");
hello.sayHello(); |
|
|
Ohne Exceptions (eine mögliche Variante)
| int hours = time.getHours(); // values < 0 are errors
if (hours < 0) return FAIL_CLOCKERROR;
Hello hello = new Hello(hours >= 12 ? "Good afternoon world!" : "Good morning world");
if (null == hello) {
if (GetSystemError() == ERR_OUT_OF_MEMORY) {
return ERR_OUT_OF_MEMORY;
} else {
return ERR_CLASS_NOT_FOUND;
}
}
err = hello.sayHello();
if (FAILED(err)) {
if (err = ERR_SECURITY) {
...
}
...
} |
|
|
Wenn time.getHours() eine Exception wirft, wird diese hier nicht abgefangen und läuft weiter nach oben. Wenn getHours() bei der Variante ohne Exceptions -1 liefert, wird die Methode abgebrochen und der Fehler nach oben signalisiert, einige andere Fehlerzustände werden auch erkannt, ob das vollständig ist oder nicht kann nur durch aufwändiges Studium der Dokumentation und des Sources der beteiligten Funktionen ermittelt werden. In beiden Situationen kann der Aufrufer dieser Methode mit der Signalisierung machen, was er will.
XSLT-Prozessor | |
JohannesDöbler? stellt unter http://www.aztrecrider.com/xslt seinen freien XSLT-Prozessor (samt 1.5 MB Sourcecode, der seine Ansichten darstellt) zur Verfügung.
Reste | |
Einiges von dem folgenden ist off-topic schrott, anderes ist redundant zu ExceptionsDiskussion. Anderes wurde einfach noch nicht zum Refaktorisieren berücksichtigt.
- Das Ignorieren einer Exception durch einen leeren Catch-Block ist ein Mittelding, das davon abhängt, wieviel und welcher Code in dem dazugehörigen Try-Block steht. Wird jeder Funktionsaufruf einzeln behandelt, so entspricht das in etwa dem Nichtbehandeln von Rückgabewerten. Wird jedoch dabei zum Beispiel die gesamte Datei-Lese-Routine übersprungen so ist das zwar nicht optimal, könnte jedoch (bis zu einem gewissen Grad) ausreichen um die Applikation in einem definierten Zustand zu belassen.
Mit einem Fehler im Programmablauf kann man imho auf zwei sinnvolle Arten umgehen:
- man behandelt ihn
- man signalisiert ihn, sodass er anderswo sinnvoll behandelt werden kann
"Sauber trennen" mit Returncodes:
Es sei zugestanden, dass ich das Beispiel in C++ formuliere. Die Notwendigkeit eines expliziten "file.close();" entfällt damit schonmal, das wird der Destruktor von File schon machen, falls es nötig ist, darüber brauch' ich mir gar keinen Kopf zu machen. (Normal, so muss es sein.)
Aus Gründen der Waffengleichheit zu IOException muß außerdem zugestanden sein, dass irgendwie (der Möglichkeiten sind viele) ein Fehlerbeschreibungsobjekt erhältlich ist.
| File file;
if (file.open("test.txt") & file.write("name=") & file.write(name) & writeContent(file)) // rufe Hilfsmethode
{
[...]
}
else
{
message("IO/Fehler: " + getError().getReason());
} |
|
|
-- vgl
- Wäre natürlich jetzt gemein, aus den drei Schreibbefehlen 30 zu machen :-) Dann sähe die &&-Konstruktion schon etwas alt aus. Den getError() gestehe ich Dir gerne zu. Aber ist getError() threadsafe? Wie wird damit umgegangen, wenn in den write-Befehlen mehrere Fehler auftreten, ... Exceptions sind da wesentlich einfacher, weil sie den Fehlergrund und -kontext eindeutig und ohne Aufwand für den Programmierer festhalten. -- jd
- Mit den 30 Schreibbefehlen sehe ich eigentlich kein großes Problem. Allerdings bricht diese Lösung zusammen, wenn ich nicht einfach nur eine Sequenz von gleichartigen Befehlen ausführen will, sondern zwischendurch auch noch andere Methoden aufrufen oder irgendwelche Kontrollstrukturen benutzen möchte.
- Alternativ/parallel zu Returncodes würde auch das klassische Durchreichen ganz gut funktionieren. Skizze:
| class MyErrorInfo : public ErrorInfo
{
public:
~MyErrorInfo()
{ if (errorHappened()) message(getReason()); }
};
[...]
{
MyErrorInfo errinfo;
File file(errinfo);
file.open("test.txt");
file.write("name=");
file.write(name);
// rufe Hilfsmethode
writeContent(errinfo, file);
} |
|
|
- File beeinflußt im Fehlerfall das Fehler-Info-Objekt und die Funktionen von File "tun im weiteren nichts", falls ein Fehler aufgetreten ist. Weitere 30 Schreibbefehle: kein Problem (kommt mir jetzt nicht mit Performanzproblemen). Threadsafe: yep. Fehlergrund und -kontext eindeutig: ja, ohne wildes Exception-Herumgespringe. Aufwand: ja, Klassen zur Fehlerinformationsdurchreichung sind zu erstellen bzw. liegen vor (reflektiert Exceptionklassen). Und: ja, die Durchreichung selbst muß erfolgen. Warum nicht, die Fehlerbehandlung wird dadurch vom Stiefkind ("Ausnahme") zum first-class citizen. -- vgl
Eine Methode, die sofort zusammenbricht, wenn Du dazwischen andere Objekte manipulierst, deren Zustand Du in einem Fehlerfall nicht verändern solltest.
- Eigentlich bist Du jetzt bei einem selbstgemachten Exceptionhandling angekommen. Nur daß das Exceptionobject leider auch erzeugt wird, wenn kein Fehler auftritt, und als expliziter Parameter mitdurchgeschleift werden muß, was Dir die Exceptionsyntax auch einfacher bieten würde. Noch nicht gelöst ist die Situation, wenn mehrere Fehler auftreten. MyErrorInfo? müsste die dann schon alle speichern können. Nicht tragfähig ist aber imho die Konstruktion, das Programm weiter zu durchlaufen, wenn irgendwo ein Fehler auftritt. Das mit der Performance kann ein reales Problem sein, oder es kann (z. B. bei der Steuerung eines Roboters) fatal sein, den Fehler erstmal zu ignorieren. -- jd
- Eigentlich bist Du jetzt ...: 1.: Aber eben ohne Exceptions, d. h. die Programmstruktur spiegelt den Programmfluss wieder; von einer Funktion, die ich aufrufe, weiß ich auch (gäbe es eben keine Exceptions), daß sie (falls überhaupt) auch nur von da wiederkehrt.
- Die Programmstruktur ohne Exceptions spiegelt den Programmfluss weit weniger gut wieder als mit! Schließlich kann ich einem Methodenaufruf bei Deiner Lösung ja gar nicht ansehen, ob er bei einem vorherigen Fehler noch etwas bewirkt oder nicht. Insbesondere ergeben sich möglicherweise erhebliche Wartungsaufwände durch erhöhte Fehleranfälligkeit, da ja sichergestellt sein muss, dass auch jede Methode jeder Unterklasse angemessen auf eine Fehlersituation reagiert.
- 2. ist es nur ein Angebot, gibt ja noch die andere oben besprochene Möglichkeit.
- Da dieses Angebot von Dir als Reaktion auf Schwächen der anderen Möglichkeit gemacht wurde, scheint mir diese Erkenntnis doch eine gewisse Bedeutung zu besitzen.
- Nur daß das Exceptionobject leider auch erzeugt wird, ..., und als expliziter Parameter mitdurchgeschleift werden muß, ...: 1. kann die Operation sehr billig sein, 2. könnten die lokalen Fehler-Info-Objekte womöglich stattdessen besser als Membervariablen in den Klassen angelegt werden, bei deren Nutzung es zu Fehlern kommen kann: Parameterübergabe fällt weg. Noch nicht gelöst ist die Situation, wenn mehrere Fehler auftreten.: Tut auch nicht nötig, der Beispielcode mit den Exceptions liefert schließlich auch nur lediglich den ersten auftretenden Fehler.
- Da hast Du recht, allerdings leistet das Dein Code wirklich nur, wenn alle Objekte (innerhalb des Methodenaufrufs) dasselbe Fehlerobjekt benutzen.
- Nicht tragfähig ist aber imho die Konstruktion ...: Natürlich ist sie tragfähig, solange das Programmverhalten das gewünschte ist. Mein Beispiel verhält sich im Fehlerfall wie das Exception-Beispiel.
- ...und liest sich auch fast genauso. Ich verstehe immer noch nicht den Vorteil, keine Exceptions zu benutzen...
- Das mit der Performance kann ein reales Problem ...: Ja, ja, das mit der Performance kann immer ein Problem sein. Totschlagargument. Und wir wollen auch nicht "im Voraus optimieren" (siehe SoftwareOptimierung).
- Ich sehe das Problem eher darin, dass ja möglichst sämtlicher Code, den ich normalerweise in einen try-Block packen würde, auch fehlersensitiv sein sollte - nicht nur die Objekte, die einen Fehler hervorrufen können, sondern auch diejenigen, die in ihrem Umfeld benutzt werden (und auf deren Code ich im Extremfall gar keinen Zugriff habe). Mit Exceptions habe ich ein Konzept, dem ich durch die standardisierte Handhabung und direkte Einbindung in die Laufzeitumgebung blind vertrauen kann, ob nun bei eigenem oder fremden Code.
- ... den Fehler erstmal zu ignorieren: Versteh' ich nicht, es wird nichts "ignoriert", jedenfalls nicht mehr, als im Exception-Beispiel. -- vgl
- Verstehe ich, ehrlich gesagt, auch nicht... ;-) -- ip
- Ich meinte, bloß, wenn "file.write("name=");" einen Fehler bewirkt, "file.write(name)", etc. auch noch ausgeführt werden.
-- jd
Was mich im Zusammenhang mit der Exception-Anwendung besonders beschäftigt ist:
- Wie wird sichergestellt, dass in einem SoftwareProjekt? jede Exception (bzw. Runtime-Fehler) auch aufgefangen wird?
- Falls das tatsächlich sinnvoll erscheint: An geeigneter Stelle ein "catch(Throwable throwable)" einführen (Java).
- Beispiel JavaServerPages: Für jede Seite kann eine ErrorPage? definiert werden, an die weitergeleitet wird, falls eine nicht gefangene Exception auftritt.
- Wie wird in Java sichergestellt, dass Resourcen (und Locks etc.) - vor allem in unbeteiligten Funktionen, die zwischen der catch-Funktion und der throw-Funktion liegen - korrekt freigegeben (bzw. aufgelöst) werden?
- Durch einen entsprechenden FinallyBlock??
Die Arbeit mit Fehlerstatus-Rückgabe ist etwas mühsamer (noch mühsamer in Java auf Grund des fehlenden goto), aber total simpel und jede Funktion kann für sich in Bezug auf korrekte Fehlerbehandlung beurteilt werden. Bei der Verwendung von Exceptions scheint mir die Sicherstellung der korrekten Fehlerbehandlung nur durch eine komplexe Zusammenschau über das Gesamtprojekt möglich. --hl
P.S. Mit Returncodes könnte eine Fehlerbehandlung vielleicht so aussehen (keine konkrete Programmiersprache gewählt):
| error=0;
file=FileOpenMess("test.txt"); // generates default error message
if(file==NULL) {
error=-1; goto finalize;
}
status=FileWriteMess(file,"name=" + name); // generates default error message
if(status) {
error=-1; goto finalize;
}
status=FileWriteContent(file);
if(status) {
message(...);
error=-1; goto finalize;
}
finalize:
FileClose(file); // tolerant gegen file==NULL
return error; |
|
|
wobei viele Variationsmöglichkeiten existieren. Sicherlich gibt es nicht das Problem der Übersichtlichkeit.
- "generates default error message" - Schön, aber wohin? Ins Logfile, auf den Bildschirm oder per Email? Muss man dann ein FileOpenMessLog, FileOpenMessScreen und FileOpenMessEmail schreiben? Ebenso dann für FileWriteMessLog, FileWriteMessScreen und FileWriteMessEmail? Und was passiert, wenn die Operation "name=" + name im Aufruf von FileWriteMess an Speichermangel scheitert? Die Arbeit mit Returncodes mag "total simpel" sein, aber nur dann, wenn auch nur simple Fehler passieren können.
(Nicht ganz ernstgemeinte Varianten verschoben nach ComeFrom.)
Mit Java-Exceptions:
| try {
file=FileOpenMess("test.txt"); // generates default error message
FileWriteMess(file,"name=" + name); // generates default error message
FileWriteContent(file);
} catch (SomeException e) {
message(...);
throw e;
} finally {
FileClose(file); // tolerant gegen file==NULL
} |
|
|
Ein besonders überzeugendes Beispiel für die Anwendung von Exceptions ist für mich die Behandlung syntaktischer Fehler in einem Recursive-Descent-Parser. Ein solcher enthält für jedes syntaktische Konstrukt eine entsprechende Analyse-Funktion, und die syntaktische Analyse eines Konstrukts involviert den rekursiven Aufruf der Analyse-Funktionen für die darin eingeschachtelten Unter-Konstrukte.
Syntaktische Fehler werden nun typischerweise irgendwo tief unten in dieser Aufrufverschachtelung entdeckt. Es wäre jedoch sehr umständlich und würde den gesamten Code gewaltig aufblähen, wenn man für jeden dieser Aufrufe einen Returncode explizit abfragen und nach oben durchreichen müsste.
- Möglicherweise ist das aber gar nicht die einzige Alternative - eine Idee wäre z. B. die Benutzung des NullObjekt Musters stattdessen. Unter Umständen ließe sich auf diese Weise das Problem rein über die Ausnutzng von Polymorphie lösen, ohne den Code durch Exception-Handling "aufzublähen"... Habe ich aber ehrlich gesagt noch nie ausprobiert. -- IljaPreuß
- Versteh' ich nicht so recht: Wie soll das NullObject?-Pattern etwa die Rückkehr zu einer "oberen" Aufruf-Ebene ähnlich wie mit Exceptions vereinfachen (s. folgenden Absatz)? --KlausGünther
- Das NullObjekt könnte die sofortige Rückkehr zu dieser "oberen" Aufruf-Ebene unnötig machen, indem es den "darunterliegenden" Ebenen ein sinnvolles default- oder "null"-Verhalten gibt. Sicherlich ist es nicht möglich, eine Exception einfach durch ein NullObjekt zu ersetzen, sondern selbiges würde einen größeren Einfluss auf das allgemeine Design haben - ich bin mir nicht sicher, ob das negativ zu bewerten ist. -- ip
Ebenso typisch ist, dass man in solchen Fehlerfällen mit der Fehlerbehandlung auf einer viel gröberen Ebene ansetzen möchte, als da wo der Fehler entdeckt worden ist. Z. B. möchte man einfach beim nächsten Statement wiederaufsetzen. Mit Exceptions kann man auf einfache Weise mit einem Schlag unter korrektem Abbau der Aufrufverschachtelung auf diese grobe Ebene zurückkehren und dort fortfahren.
Siehe auch ExceptionsInSpracheLava.
--KlausGünther
Merkwürdig, tagsüber komme ich nicht an diese Seite ran, sie wird von der Filterliste der Firmenfirewall blockiert.
Für Exception-Beispiele brauche ich nur in einer Smalltalk Entwicklungsumgebung nach den dort vorhandenen Exceptions suchen. Die meisten werden schnell einleuchten, andere sind exotisch:
- Arithmetikfehler: Zuweisungssemantik lässt herkömmliche Returncodeabfrage nicht zu. Bleiben globale Errorcodes, pfui.
- Fehlermeldungen aus der Virtuellen Maschine, etwa Speicherknappheit: Ich glaube, hier wird man sich mit Alternativen zu Exceptions besonders schwer tun.
- Breakpoints: In Smalltalk werden spezielle Exceptions so verwendet, wie anderswo Breakpoints. Es gibt auch Debugger mit eigener Breakpointverwaltung, aber der Klassiker ist, einfach ein 'self halt' in den Code zu schreiben und damit eine Exception zu provozieren. Klingt abstrus, weil das nur dann Sinn macht, wenn der Turn-Around-Zyklus wegfällt.
- Notifikationen, etwa Ende eines Streams: Anstatt vor jedem 'next' mit 'atEnd' abzufragen, ob der Strom schon am Ende ist, kann man auch einfach loslaufen, bis es knallt. Hat ein bisschen was von Lazy Evaluation. Arbeitet man massiv mit Wrappern, kann das entgegen dem ersten Empfinden zu eleganterem Code führen.
- Programmier- und Laufzeitfehler: Vergessen, eine abstrakte Methode zu implementieren? Eine als gesperrt markierte Methode aufgerufen? Eine Methode an ein Objekt geschickt, dass diese Methode nicht implementiert? In Smalltalk sind das alles Exceptions. Der Bytecodecompiler warnt nur vor grobem Unfug, die allermeisten Prüfungen erfolgen erst zur Laufzeit. Es ist offensichtlich, dass man hier einen sehr mächtigen Debuggingmechanismus benötigt und testgetrieben entwickelt. Dafür gibt das Smalltalk Laufzeitsystem jeder unbehandelten Exception den gesamten aufrufenden Kontext mit. In Smalltalk sind Exceptions übrigens proceedable.
Ein gutes allgemeines Beispiel sind hangeschriebene Parser. (Ist der Parser generiert, stört mich der unübersichtliche Code nicht ;-) Dabei brauchen die Exceptions gar nicht unbedingt bis in die Modulschnittstelle bekannt gemacht werden. Es reicht völlig, sie modulintern zu verwenden. Der Parser hat mitunter eine total simple Schnittstelle, ist aber intern kompliziert. Fehlerbehandlung ist nur rudimentär möglich, da die interne Struktur des Parsers hinter der Schnittstelle verborgen bleibt. Fehlermöglichkeiten gibt es aber i. d. R. sehr viele. Verhinderung unbehandelter Exceptions ist einfach, weil die Schnittstelle schlank ist. Die mögliche Reaktion des Nutzers bezieht sich eher auf die Datenquelle denn auf den Parsevorgang selbst. Im Effekt benötige ich also einen raschen, geregelten Abbruch des Parsevorgangs und Information über die Art des Fehlers. Für soetwas ist der Exceptionmechanismus gut gerüstet.
-- SaschaDördelmann
- Ok, arbeiten wir es Punkt für Punkt ab. Ich sehe auf Anhieb keine Chance für "Arithmetikfehler" (es sei denn, ich habe eine Programmiersprache mit schrottiger Arithmetik). Kannst du ein Beispiel nennen? -- VolkerGlave
- Die IEEE floating point exceptions: z. B. NaN, Division durch 0, INF, Over- und Underflow u. v. m. werden von vielen modernen Prozessoren implementiert und daher von praktisch allen Programmiersprachen auch so übernommen.
- Das steht hier ja gerade zur Debatte, ob es von Vor- oder von Nachteil ist, diese Dinge als sog. "Exceptions" zu übernehmen. Meiner Ansicht nach ist es von Nachteil. Siebenkommafünf geteilt durch Null ist INF. Keine Problem. Erst recht kein "Fehler". -- vgl (erst Mo. wieder vor Ort)
- Division durch 0 durch irgendeine als Unendlich festgeschriebene Konstante zu ersetzen ist aber leider mathematisch falsch und entspricht auch nicht dem IEEE Standard. Exceptions sind eine allgemeine Lösung für viele Probleme. Lösen wir uns doch mal von den "Fehlern" und konzentrieren uns darauf, dass "exception" englisch für "Ausnahme", "Ausnahmeregelung" oder "Einwand" heisst und das Konzentrieren auf Fehlerbehandlung daher nicht Zielführend ist.
- Jetzt wird es hier langsam blödsinnig. Dass Division von Siebenkommafünf durch Null Unendlich ergibt, ist nicht "leider mathematisch falsch", sondern (ohne leider) mathematisch richtig. Und es entspricht dem IEEE Standard, indem es hier "Inf" ergibt. -- vgl
- Na ja, mathematisch ist es tatsächlich nicht richtig. Das spielt aber bei dieser Diskussion nicht so große Rolle. Was ist das Ergebnis von: 12 + "a" / 2i?
- "..., mathematisch ist es tatsächlich nicht richtig.": Ist es wohl. Offenbar kommen wir hier nicht zusammen. Ich hätte jedenfalls gerne eine Programmiersprache, bei der mathematisch korrekt Unendlich herauskommt. "Was ist das Ergebnis von: 12 + "a" / 2i?": Bei kompilierenden Sprachen hätte ich gerne in der Kompilephase Abbruch mit etwas in der Art "Syntax error ...", bei interpretierenden Sprachen _unmittelbaren_ Abbruch der Ausführung mit ebenfalls etwas in der Art "Syntax error ...". -- vgl
- ... Ist es wohl. Na ja, sagen wir es so: INF ist eine praktische Lösung der IEEE, mit der Defintion der mathemantischen Division der realen Zahlen hat es aber wenig zu tun. Schon deswegen, weil INF keine reale Zahl ist :-) Nun aber zum Thema: Du hast Recht, dass kompilierte Sprachen bei 12 + "a" / 2i einen Compilefehler melden könnten. Man könnte z.B. in C++ aber auch Klasse definieren, die "beliebige" Werte halten kann (Integer, Float, String, Datum, Complex, ...) und die mathematische Operatoren implementiert. Nennen wir sie z.B. Var. Den Fehler im Ausdruck "Var(12) + Var("a") / Var(0, 2)" würde der Compiler schwer erkennen können.
- Es kann also auch bei kompilierten Sprachen zu Laufzeitfehlern innerhalb der Ausdruckauswertung kommen. Wie reagiert man auf solche Fehler? Dein Vorschlag, das Programm abzubrechen, ist sicherlich eine Möglichkeit. Warum sollten aber gerade Ausdruckauswertungsfehler eine Ausnahme bilden? Warum sollten sie nicht mit anderen Laufzeitfehlern gleichbehandelt werden? Was, wenn der Ausdruck nicht in Quelltext steht, sondern vom Benutzer eingegeben wird? Sollte auch dann das Programm abgebrochen werden müssen?
- Übrigens, wenn man damit rechnen muss, dass ein Statement zum Programmabbruch führt, ist die Codesequenz a; b; c; nicht mehr so klar. Ein Abbruch erinnert irgendwie an das Werfen einer Exception - nur mit dem Unterschied, dass der aktuelle Prozess ihn nicht "fangen" und nicht "abräumen" kann. Dass muss dann das aufrufende Programm übernehmen. -- GregorRayman
- (Randnotiz: Du meinst vermutlich "reelle Zahlen", nicht "reale Zahlen".) "Warum sollten aber gerade Ausdruckauswertungsfehler eine Ausnahme bilden?": Sollen sie nicht, habe ich auch nicht gesagt. "Warum sollten sie nicht mit anderen Laufzeitfehlern gleichbehandelt werden?": Sie sollen gleichbehandelt werden. Unmittelbarer Programmabbruch. "Was, wenn der Ausdruck nicht in Quelltext steht, sondern vom Benutzer eingegeben wird? Sollte auch dann das Programm abgebrochen werden müssen?" Ja. Wenn das Verhalten nicht akzeptabel ist (und das ist es normalerweise nicht), dann ist die Benutzereingabe vor der Bewertung eben zu prüfen. Muss eh getan werden, wenn man eine brauchbare Benutzermeldung präsentieren will. Wird seit Tausend Jahren auch so gemacht. "Dass muss dann das aufrufende Programm übernehmen." Über ein "aufrufendes Programm" reden wir hier nicht. Allerdings: Das eigentliche Programm ist so zu schreiben, dass es nicht (von sich aus) abbricht. Tut es das doch, dann enthält es entweder noch einen Programmierfehler, der zu korrigieren ist. Oder das Verhalten wird so akzeptiert. -- vgl
- :-) Ja ich meinte die reellen Zahlen.
- "Warum sollten sie nicht mit anderen Laufzeitfehlern gleichbehandelt werden?": Sie sollen gleichbehandelt werden. Unmittelbarer Programmabbruch.
- Und ich dachte, andere Fehler können durch einen Fehlercode signalisiert werden. Oder habe ich Dich falsch verstanden?
- "Was, wenn der Ausdruck nicht in Quelltext steht, sondern vom Benutzer eingegeben wird? Sollte auch dann das Programm abgebrochen werden müssen?" Ja. Wenn das Verhalten nicht akzeptabel ist (und das ist es normalerweise nicht), dann ist die Benutzereingabe vor der Bewertung eben zu prüfen. Muss eh getan werden, wenn man eine brauchbare Benutzermeldung präsentieren will. Wird seit Tausend Jahren auch so gemacht.
- Das ist aber schon eine andere Behandlung als bei anderen Fehlern, nicht wahr? Bei anderen Fehlern willst Du deren Auftreten zulassen, und über Fehlercode signalisieren; bei Ausdrücken darf der Fehler nicht passieren. Beispiel: a) Der Benutzer gibt den Namen einer nicht vorhanderer Datei ein. Das Programm versucht sie zu öffnen und gibt statt eines Filehandle den Fehlercode -1 zurück. b) Der Benutzer gibt den hypotehtischen Ausdruck "Filecontents(nichtvorhanden.txt)" ein. Warum kann man es nicht gleichartig behandeln? Warum sollte das Programm zuerst überprüfen, ob bei der Auswerung nicht ein Fehler droht? Was ist, wenn die Fehlersituation erst nach der Überprüfung ensteht? (z.B. weil die Datei danach gelöscht wird?)
- Übrigens: Wir besprechen hier nur Fehler, die in einem korrekten Programm passieren können. Programmierfehler sind ein anderes Thema. -- gR
- Bevor wir hier dutzende neue Fässer aufmachen, möchte ich gerne den Fall "Siebenkommafünf geteilt durch Null" (als ein Beispiel vermeintlicher "Arithmetikfehler") zuende behandeln. Hier nochmal meine Wunschvorstellung: _Entweder_ soll die Programmiersprache es hergeben, dass dabei ein plausibles Rechenergebnis - üblicherweise Unendlich - herauskommt. So handhaben es bspw. die Sprachen SpracheJ (7.5 % 0 ergibt _, _7.5 % 0 ergibt __) und SpracheK (7.5 % 0 ergibt 0i, -7.5 % 0 ergibt -0i). _Andernfalls_, wenn die Programmiersprache so definiert wird, dass bei "Siebenkommafünf geteilt durch Null" kein plausibles Rechenergebnis herauszubekommen ist, wünsche ich, dass, sollte dennoch diese Rechnung durchzuführen versucht werden, das Programm unmittelbar abgebrochen wird (natürlich mit irgendeiner zugehörigen ausführlichen Hinweismeldung, sofern in der Umgebung der Programmiersprache die Meldung von Hinweisen vorgesehen ist). Das bedeutet dann natürlich, dass bei solchen Programmiersprachen der Programmierer von vorneherein dafür zu sorgen hat, dass solche Fälle nicht eintreten; tritt es doch ein (Folge: Abbruch), hat er noch einen zu korrigierenden Programmierfehler in seinem Programm, oder er gibt sich zufrieden mit diesem Abbruchverhalten. -- vgl
"Der Benutzer gibt den Namen einer nicht vorhanderer Datei ein. Das Programm versucht sie zu öffnen und gibt statt eines Filehandle den Fehlercode -1 zurück."
Warum lassen wir es hier überhaupt erst zu einem Fehler kommen? Warum kann das Programm nicht erstmal prüfen, ob die Datei vorhanden ist, bevor es versucht, sie zu öffnen?
- Weil es nichts bringen würde. Im Zeitraum zwischen der (noch klappenden) Prüfung und dem folgenden Zugriffsversuch kann die Datei unzugreifbar geworden sein. Es ist schon ok, den Zugriffsversuch zu machen, und dann den Ergebniswert des Zugriffsversuchs zu bewerten. (Wobei die Bewertung entfallen kann, sofern die Weiterverarbeitung unabhängig vom Ergebniswert ist und der Ergebniswert eine Nicht-Bewertung zulässt.) -- vgl
Eben, es würde nichts bringen, vorab zu prüfen. Warum sollte diese Situation anders gehandhabt werden, als wenn der Benutzer einen Ausdruck eingibt (der das Programm desn Ausdruck aus einer Datei einlißet), dessen Auswertung ab und zu zu einem Fehler führen kann? Warum soll hier nur die Möglicheit bestehen, einen falschen Wert oder einen Abbruch zu liefern?
Übrigens: Eine Exception zu werfen ist, was den abbrechenden Code angeht, dem Abbruch eines Programmes gleich. -- gR
- (a) "Übrigens: ...": Kommt es nicht. Eine Exception kann abgefangen werden. Der Abbruch kann und soll es nicht.
- Ich sage ja: "was den abbrechenden Code angeht"
- (b) "Eben, es würde nichts bringen, vorab zu prüfen. Warum sollte diese Situation anders gehandhabt werden, ...": Weil die Situation anders ist. In diesem Fall kann vorher geprüft werden, da zwischen Prüfung und Bewertung keine Änderung des Ausdrucks mehr stattfindet.
- Es können doch Daten geändert werden, auf denen der Ausdruck basiert. z.B. Ausdruck: "Maximaler Eintrag in der Datei X".
- Deine beiden Punkte "Übrigens: ..." und "Eben, ..." sind also falsche Aussagen. Ich verstehe nicht, warum du solche Aussagen machst. Wenn das so weiter geht, verzichte ich auf eine weitere Diskussion. -- vgl
- Sie sind nicht falsch, bloß missverstanden :-) In Gegenteil, Deine Aussage, dass ein Abbruch nicht abgefangen werden soll, ist falsch ;-)
Exceptions vs. Assert | |
Hier ist schon mehrfach geäußert worden, Exceptions sollten nicht dazu dienen, Programmierfehler aufzudecken. Ich teile diese Einschätzung nicht. Sie fußt vielleicht auf der Angst, jemand könne durch Behandlung von Exceptions Programmierfehler verschleiern. Ich will nicht ausschließen, dass das gelegentlich vorkommen kann, aber ich ziehe Exceptions einem harter Abbruch mit einem C-style assert trotzdem vor. Ich bin darüber hinaus auch mit ThePragmaticProgrammer einig darüber, dass Assertions im Produktivcode nicht deaktiviert werden sollten. -- SDö
- "Ich will nicht ausschließen, dass das gelegentlich vorkommen kann, aber ich ziehe Exceptions einem harter Abbruch ... trotzdem vor.": Schön und gut, dass du das vorziehst, leider verschweigst du das Entscheidende: Warum ziehst du es vor? Ein harter Abbruch würde dich dazu zwingen, das Programm an der fehlerprovozierenden Stelle richtigzustellen (etwaig das Assert selbst). Du möchtest aber darüber hinaus noch die Freiheit haben, an einer Reihe von nicht-fehlerprovozierenden Stellen eingreifen zu können. Das Verschleiern-Können hast du also bereits eingeplant. Besten Dank. -- vgl
- Eine nichtbehandelte Exception mündet ebenfalls in einen Programmabbruch. Exceptions sind Informationsträger und erleichtern das Debuggen. Deine Polemik ist deplaziert.
- Du hast aber die Chance, sie zu behandeln, und dann also nicht notwendigerweise einen Abbruch. D. h. du hast die Chance, zu verschleiern. Bei hartem Abbruch hast du die Chance nicht, da muss der Fehler beseitigt werden. Harte Abbrüche führen also auf längere Sicht (sofern das Programm überhaupt noch gewartet wird) zwangsläufig zu robustem, weil Fehlerursachen-beseitigtem Code, wohingegen Exceptions auf längere Sicht (sofern das Programm überhaupt noch gewartet wird) zwangsläufig zu aufgeblähtem, klapprigem, weil hier und da Fehlerursachen-bewahrendem und nur Symptom-beseitigendem Code führt. -- vgl
- Das ist zu pauschal. Es gibt sogar Programme, die dürfen gar nicht abstürzen und müssen ggf. im laufenden Betrieb gepatcht werden. Dann gibt es evtl. Programmteile, auf die ich nur bedingt Zugriff habe und die daher ebenfalls nicht abstürzen dürfen. Wenn sie es doch tun, muss ich mir Gedanken bzgl. der Stabilität des Gesamtsystems machen und greife auf Alternativen zurück. Die Grenzen zwischen Daten- und Programmierfehler sind zudem fließend: Ein nicht abgefangener Datenfehler kann ein Programmierfehler oder ein gewollt in Kauf genommener Kompromiss sein. Es gibt also Fälle, in denen eine Art Pauschalbehandlung aller Fehler Sinn macht. Diese Pauschalbehandlung muss der Wartung keineswegs entgegenstehen. Ich kann z. B. den Benutzer vor dem eigentlichen Programmabbruch darüber informieren, dass sich das Programm zwangsbeendet! Dafür muss zwischen einem aus der Business-Logik kommenden Fehler und dem Programmabbruch ein Dialog geschaltet werden. Mit Exceptions nur ein Anwendungsfall eines allgemeinen Prinzips. Deinem "da muss der Fehler beseitigt werden" halte ich entgegen: Es kann im Gegenteil dazu führen, dass der Kunde das Vertrauen in dein Programm verliert, weil sich dieses nicht einmal geordnet beendet.
- "Es gibt sogar Programme, die dürfen gar nicht abstürzen und müssen ggf. im laufenden Betrieb gepatcht werden.": "Sogar"? Lustige Einstellung. _Kein_ Programm darf abstürzen. Im übrigen redet hier niemand von abstürzen. "Die Grenzen zwischen Daten- und Programmierfehler sind zudem fließend: Ein nicht abgefangener Datenfehler ...": Schnurzpiepe ob die fließend sind, ich zumindest rede hier ("Exceptions vs. Assert") _ausschließlich_ über Programmierfehler. "Ich kann z. B. den Benutzer vor dem eigentlichen Programmabbruch darüber informieren, dass sich das Programm zwangsbeendet!": Kannst du nicht, weil vorher ein unerwarteter Fehler passiert ist, und dehalb unbekannt ist, ob noch Ressourcen für die Benutzer-Information bestehen. Richtig ist aber, dass selbstverständlich nach Programmabbruch der Benutzer über die Zwangsbeendigung informiert werden kann. "Es kann im Gegenteil dazu führen, dass der Kunde das Vertrauen in dein Programm verliert, weil sich dieses nicht einmal geordnet beendet." Es wird geordnet beendet. Das Programm muss korrigiert werden, anschließend passiert derselbe Fehler nicht mehr. Wüsste nicht, wieso das Korrigeren von Fehlern vertrauenmindert sein sollte. -- vgl
- Zeig doch bitte mal was man mit der Rückgabe eines Fehlercodes machen kann was man mit Exceptions nicht machen kann. Ich behaupte da gibt es gar nichts. Exceptions vereinheitlichen die Fehlerbehandlung. Wie und wo Fehlerinformation zu finden sind, sollte nun keine Willkür der Bibliotheksentwickler sein (wie GetLastError?, GetLastSystemError?, GetErrorText?, GetDbError? oder mal den Fehlercode als Parameter, mal als Rückgabewert, malt mit Fehlertext, mal ohne). Fehlerinformationen gehören in Exception-Objekt. Da sucht man sie, da findet man sie. Durch Exceptions läß sich die Anzahl der Parameter vieler Prozedure vereinfachen. Ob man Programme mit Fehlern schreibt ist keine Frage ob man Exceptions verwendet oder nicht. Ich bin mir sicher, du würdest mit deinem Fehlerbewustsein in einem Programm das nur Exceptions verwendet nicht mehr Fehler produzieren als jetzt. Und ich auch nicht. hz
- PS. Was sind eigentlich deine Ziele in dieser Diskussion? Möchtest du Exceptions aus dem Sprachschatz der Programmiersprachen verband wissen (so nehme ich an, da du sie ja strikt ablehnst) und als Fehlentwicklung Brandmarken? hz
- "Was sind eigentlich ... Ziele in dieser Diskussion?": Ziel dieser Seite und verwandter Seiten (z. B. ExceptionsDiskussion) ist es, zu klären, ob Exceptions eher von großem Vorteil oder eher von großem Nachteil (und also eine Fehlentwicklung) sind. Bislang ist eine Klärung nicht in Sicht. Meine Meinung ist "Fehlentwicklung", richtig. -- vgl
- Mich interessierten eigentlich nur "deine" Ziele. Meine Meinung nach, ist "Fehlentwicklung" falsch. Desweiteren glaube ich das die Frage, ob Exceptions Vor- oder Nachteile bringen, jeder schon für sich geklärt hat. Und ohne Exceptions, geht es auch nicht mehr. Zumindest bei Delphi, Java und C# sind Exceptions Pflicht.
- Deine letzten beiden Sätze sind für die Diskussion uninteressant. Dass es Sprachen gibt, die das Sprachmittel "Exception" haben, heißt noch lange nicht, dass Exceptions eher von Vor- als von Nachteil sind. In C# ist man immerhin schon mal zur Einsicht gelangt, Member-Exceptionspezifikationen für schädlich zu halten (CheckedExceptionsConsideredHarmful). Außerdem sind Exceptions bei den von dir genannten Sprachen insofern nicht Pflicht, als man bei eigenen Algorithmen selbst entscheiden darf, sie mehr oder weniger (oder gar nicht) einzusetzen. -/- Mein Ziel ist, zu erfahren, ob Exceptions eher von großem Vorteil oder eher von großem Nachteil (und also eine Fehlentwicklung) sind. Ich denke nicht, dass das eine Geschmacksfrage ist, sondern glaube noch, dass sich das prinzipiell klären lassen müsste. So, wie das für "goto" auch irgendwann mal geklärt worden ist (und zwar so, dass das Thema "Einsatz von goto" um Größenordnungen weniger strittig ist als "Einsatz von Exceptions"). Natürlich bin ich nicht so naiv, zu glauben, dass wir das hier im DseWiki klären werden. Da müsste schon jemand mit richtig viel Ahnung kommen und einen Bericht "ExceptionsConsideredHarmful" verfassen. -- vgl
- Exceptionspezifikationen sind ein Thema für sich und haben nichts mit der prinzipiellen Entscheidung für oder gegen Exceptions zu tun. Ich für meinen Teil habe die Frage ob Exceptions oder ob nicht längst für mich geklärt. Du scheinbar auch. Mit einer allgemeinen Entscheidung gegen Exceptions ist m. E. nicht zu rechnen. -- SDö
- "Exceptionspezifikationen ...": Da ist unsere Wahrnehmung unterschiedlich. Ich halte ihre Strittigkeit für ein Indiz der Strittigkeit des ganzen Themas. "Mit einer allgemeinen Entscheidung gegen Exceptions ist m. E. nicht zu rechnen.": 1957 wurde die in Folge populäre SpracheFortran erfunden, natürlich mit "goto"s. Erst mehr als zehn Jahre später wurde die Erkenntnis GoToConsideredHarmful allmählich Mehrheitsmeinung. Und erst nach weiteren über 25 Jahren wurde 1994 die in Folge populäre SpracheJava erfunden und dabei "goto"s wg. dieser Erkenntnis weggelassen (und zwar - völlig bewußt - trotz des Umstandes, dass "goto"s in gewissen exotischen Situationen ganz praktisch sein mögen). Bei Exceptions ist heute Fakt, dass sie in Teilen der Community für gefährlich gehalten werden, wenn auch in der Minderheit. Niemand, der logisch denkt, würde jedoch - z. B. angesichts des "goto"s-Beispiel - heute überzeugt die Aussage machen, dass mit einer allgemeinen Entscheidung gegen (oder für) Exceptions zukünftig zu rechnen sei. Das ist heute nicht (auch nicht tendenziell) entscheidbar. Selbstverständlich ist übrigens denkbar, dass statt Exceptions ja/nein eine irgendwie "dazwischen" liegende Lösung gefunden wird, so wie statt "goto" die abgeschwächten Formen switch/case/default/break/continue/(early) return/Polymorphie/... gefunden wurden. -- VolkerGlave
- Volker, Dein Goto-Beispiel hinkt vorne und hinten. Als Goto erfunden wurde, gab es überhaupt keine Alternative dazu. Erst mit der Einführung von Unterprogrammen, kamen Gotos in Verruf. Die Diskussion um Gotos hat wahrlich eine lange Geschichte, aber ich kann mich nicht erinnern, dass über den Sinn von Unterprogrammen diskutiert wurde. Exceptions wurden erfunden, als die von dir gepriesenen Fehlercodes, die Standardmethode zur Fehlerbehandlung waren. Warum also hat man sie erfunden? Langeweile? Ich behaupte aus reiner Unzufriedenheit mit den Fehlercodes. Wenn man Goto als Beispiel nimmt, dann ist es richtiger Goto mit den Fehlercodes und Exceptions mit Unterprogrammen gleichzusetzen. Die Zukunft mag was besseres bringen, aber bis dahin werden wir auch Exceptions benutzen. Und zu Fehlercodes, als einzige Möglichkeit der Fehlerbehandlung, führt kein Weg zurück. hz
- Du bist spät in die Diskussion eingestiegen und hast offenbar erst einen Bruchteil nachgearbeitet. Ich habe nicht Fehlercodes als einzige Möglichkeit der Fehlerbehandlung gepriesen. (Nur ein Beispiel: Bei Arrayzugriff mit falschem Index plädiere ich für sofortigen Programmabbruch, statt 'ne Exception zu schmeißen. Hat also absolut nichts mit Fehlercodes zu tun.) "Als Goto erfunden wurde, gab es überhaupt keine Alternative dazu.": Selbst wenn das stimmen würde (was ich nicht glaube), das war nicht mein Punkt. Mein Punkt war, dass selbst nachdem Gotos in Verruf gekommen waren, es noch über 25 Jahre gedauert hat, bis eine populäre Programmiersprache das Goto aus ihrem Sprachschatz weggelassen hat. Dies wiederum war ein Gegenpol zum Statement von Sascha, dass seines Erachtens mit einer allgemeinen Entscheidung gegen Exceptions nicht zu rechnen sei, um nämlich darauf hinzuweisen, dass vermeintlich feststehende Entscheidungen nach Jahrzehnten kippen können. "Die Zukunft mag was besseres bringen, aber bis dahin werden wir auch Exceptions benutzen.": Und wir werden es diskutieren und genau das tun wir hier. So what. -- vgl
- Volker, du schreibst gerne viel und machst es damit lästig auf alle deine Behauptungen und "Argumente" einzugehen. Doch heute werde ich mir mal dafür Zeit nehmen. "Du bist spät in die Diskussion eingestiegen", trotzdem habe ich sie von Anfang an verfolgt. "und hast offenbar erst einen Bruchteil nachgearbeitet", weil ich nicht das schreibe was du lesen möchtest? "Ich habe nicht Fehlercodes als einzige Möglichkeit der Fehlerbehandlung gepriesen.", wieso teilst du uns das mit, ich habe nirgendwo so etwas behauptet. Ich schrieb "die von dir gepriesenen Fehlercodes" und finde dass das immer noch eine haltbare Aussage ist. "(Nur ein Beispiel: Bei Arrayzugriff mit falschem Index plädiere ich für sofortigen Programmabbruch, statt 'ne Exception zu schmeißen)". endlich eine interessante Ausage. So kann man höchstens handeln, wenn man Programmierer des Hauptprogramms ist. Programmiert man Komponenten oder Bibliotheken führt so eine Einstellung dazu, dass zB Microsoftprogramme so funktionieren wie wir das gewohnt sind und Benutzer um den Lohn ihrer Arbeit gebracht werden. "Selbst wenn das stimmen würde (was ich nicht glaube)", dein Glaube hilft uns nicht weiter. Dann kommt blah, blah, blah "um nämlich darauf hinzuweisen, dass vermeintlich feststehende Entscheidungen nach Jahrzehnten kippen können.", auf das, meinst du uns aufmerksam machen zu müssen. Arrogant. "Und wir werden es diskutieren", du bist der Meinung du diskutierst hier? Auf mich wirkt es eher so, als wolltest du aus dieser Seite, eine Volker-G.-Gedächtnis-Seite machen. hz und andere
- Bei einem falschen Array-Zugriff, kann an der Stelle wo er erkannt wird, nicht entschieden werden, ob ein Programmabbruch sinnvoll ist, da dort der Aufruf-Kontext unbekannt ist. Viel mehr muß der Fehler entlang der Aufrufkette hochgereicht werden, bis er zu einer Methode gelangt, die entscheiden kann ob die korrekte Ausführung des Unterprogramms so existensiell ist, dass bei einem Fehler das Programm abgebrochen werden muß. Denn evt. reicht es auf den Fehler zu reagieren und einen neuen Versuch zu starten oder ihn zu melden (Protokolldatei oder Messagebox).
- Beispiel: Ich schreibe in einer Textverarbeitung einen umfangreichen Text und füge dann eine Grafikdatei ein, die einen Fehler enthält und somit einen OutOfRangeError?? provoziert. Statt eines Programmabruchs erwarte ich eine Meldung "Datei konnte wegen ... nicht geladen werden.". In einer anderen Situation, z.B. wenn die Grafikdatei während eines Batch-Jobs geladen wird, mag ein Programmabbruch sinnvoll sein.
- Ich komme für mich zu dem Schluß, dass jede Methode, in dessen Kontext es nicht möglich ist, zu entscheiden, ob auf ihre Dienste verzichtet werden kann, ein Fehler an die aufrufende Methode gemeldet werden muß. Und um Fehlerart/-code/-meldung weiter zu reichen, gibt es für mich momentan nichts besseres als Exceptions. Denn selbst wenn sich keiner um die Exception kümmert, kommt es zu einer Reaktion des Programms. hz
- Ein nicht vom Benutzer initiiertes Programmende ist ein Absturz. Man kann den Effekt jedoch mildern, indem man einen Dialog vorschaltet. Fehlende Ressourcen würde ich als davon separierbares Problem sehen. Das ist aber so speziell, dass eine Diskussion nicht sprachunabhängig erfolgen kann. Dass die Fehlerkorrektur eine das Vertrauen mindernde Maßnahme ist, habe ich nie behauptet. -- SaschaDördelmann
Siehe ExceptionsConsideredHarmful, WardsWiki:UseExceptionsInsteadOfErrorValues und EinSchlechtesExceptionBeispiel
KategorieProgrammierBeispiele KategorieDiskussion KategorieException
StartSeite | DavidSchmitt/ | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern
Text dieser Seite ändern (zuletzt geändert: 21. Januar 2004 19:56 (diff))