Verschiedene Programmiersprachen haben Exceptions. Wie die funktionieren und wie man die benutzt habe ich verstanden. Allerdings will mir nicht so recht einleuchten, warum deren Verwendung sinnvoll ist: Wenn eine Funktion verschiedene Operationen durchführt, bei denen Fehler auftreten können (Datei öffnen, Daten lesen, Daten schreiben, ...) könnte diese Funktion einen großen try-catch-Block verwenden, um alle Fehler in diesen einzelnen Operationen abzufangen. Dabei wäre es wohl sinnvoll, wenn der catch-Block die Änderungen an den Daten, mit denen die Funktion hantiert, wieder rückgängig macht oder, falls das nicht funktioniert oder nicht möglich ist, die Daten in einen definierten "Reset-Zustand" versetzt, damit derjenige, der die Funktion in seinem Code verwendet (auch über mehrere Aufrufs-Ebenen hinweg), sicher weiß, was im Fehlerfall mit den Daten passiert. Doch der catch-Block weiß ja gar nicht, welche der Operationen fehl geschlagen ist, und kann daher die zuvor erfolgreich durchgeführten Operationen nicht rückgängig machen. Man braucht also bei jedem Aufruf einer Unter-Operation einen eigenen catch-Block, der die Änderungen wieder zurücknimmt. Aber das wäre ganz schön umständlich, sodass die Verwendung von Rückgabewerten zur Fehlererkennung doch einfacher gewesen wäre. Pseudocode-Beispiel: void KonvertiereDatei () { Eingabe-Datei öffnen. try { Ausgabe-Datei öffnen. } catch { Eingabedatei schließen. throw; } try { Daten lesen. Daten schreiben. } catch () { Eingabedatei schließen. Ausgabedatei schließen. Ausgabedatei löschen. throw; } Eingabedatei schließen. Ausgabedatei schließen. } Das wirkt ziemlich umständlich, und im Prinzip nur wie eine komplizierte Umformulierung der Verwendung von Rückgabewerten. Was habe ich da falsch verstanden? Wie verwendet man Exceptions "sauber"?
Du muß natürlich WENN du das willst für jede Operation einen Block haben. Z.B. wenn die Datei nicht gelesen werden kann könnte das mehrer Ursachen haben. Deshalb könnte man eine (generelle) Exeption haben die 'DateiOpenException'... von dieser sind weitere abgeleitet: DateiOpenException -> ExistiertNichtException -> SchreibgeschütztException -> KeineZugrifsrechteException Dein Möglichkeiten mit der "HauptException": - Einfach sagen Öffnen geht nicht - Auswahl bieten eine andere Datei zu öffnen - an die nächst höhere Ebene "weiterwerfen" Du könntest aber auch "ExistiertNichtException" abfangen und dann eine neue Datei anlegen und sonst die Exeption weitergeben... oder oder. Die höhere Instanz kann dann entscheiden ob sie eine Möglichkeit hat den Fehler zu behandeln... Zudem kannst du auch den "StackTrace" verfolgen um zu sehen wo das Problem auftrate... wenn die Funktion X dir als Rückgabewert nen Fehler meldet, weißt du nicht obs unterfunktion A oder B war... oder vieleicht ein Fehler den du noch garnicht bedacht hast. Man hat mit Exceptions also schon weitere Möglichkeiten... Der große Vorteil ist einfach das du die Exception behandeln KANNST aber nicht MUßT.
1 | int KonvertiereDatei(){ |
2 | if (Eingabe-Datei öffnen != 0) { |
3 | return 1; |
4 | }
|
5 | if(Ausgabe-Datei öffnen != 0) { |
6 | return 2; |
7 | }
|
8 | if (Daten lesen != 0) { |
9 | return 3; |
10 | }
|
11 | if (Daten schreiben != 0) { |
12 | return 4; |
13 | }
|
14 | return 0; |
15 | }
|
Ist auch nicht viel besser oder? mit Exceptions könntest du aber machen:
1 | void KonvertiereDatei () throws Dateifehler{ |
2 | Eingabe-Datei öffnen; |
3 | Ausgabe-Datei öffnen; |
4 | Daten lesen; |
5 | Daten schreiben; |
6 | }
|
Wenn jezt in irgeneiner Funktion ein Dateifehler auftritt, könnte die übergeordnete Insztanz z.B. versuchen festzustellen das die Datei nicht gelesen werden konnte, und es dann nochmal mit einer anderen Datei probieren. Das es danach "normal"/definiert weitergibt, darum kümmert sich "das System", der Programmierer muß dies nicht tun (also das keine korrupten/ungültigen Speicherinhalte entstehen).
> Du könntest aber auch "ExistiertNichtException" abfangen und dann eine > neue Datei anlegen und sonst die Exeption weitergeben... oder oder. Das ginge mit Rückgabewert+errno auch, wäre aber vermutlich mehr Schreibarbeit. > Die höhere Instanz kann dann entscheiden ob sie eine Möglichkeit hat den > Fehler zu behandeln... Wenn man überall konsequent Rückgabewerte+errno verwendet, sollte das wohl auch gehen, aber diese Weiterleitung würde wohl auch die Tastatur abnutzen. > Zudem kannst du auch den "StackTrace" verfolgen um zu sehen wo das > Problem auftrate... Gut, das geht mit klassischen Methoden nicht, klarer Vorteil. > Der große Vorteil ist einfach das du die Exception behandeln KANNST aber > nicht MUßT. Es ging mir jetzt um den Idealfall, wenn man alle Möglichkeiten behandeln will. Wenn man aber einen Rückgabewert ignoriert, dürfte das wahrscheinlich schwieriger aufzufinden sein als ein fehlendes try{}-catch{} (wo eigentlich eines hingehörte). > Ist auch nicht viel besser oder? Das es besser ist habe ich nicht behauptet :) > mit Exceptions könntest du aber machen: ... Okay, das wäre aber ziemlich "unsauber", weil dann der Aufrufer schwer sagen kann, was schiefgelaufen ist, und was jetzt mit den Dateien passiert ist. Wenn man das aber sowieso nicht beachten will, aber dennoch ein Fehler auftritt, ist er mit exceptions wohl leicher zu finden. Vielen Dank, ich denke, jetzt habe ich den Sinn von Exceptions verstanden. In einer perfekten Welt mit hyperinelligenten Programmierern braucht man sie wohl nicht, aber die haben wir ja nunmal nicht :)
Exceptions sind aber ein bestimmter Fehler, eine Fehlernummer ist nur eine Zahl! z.B. int x = log(0); Was willst du als Fehlernummer zurückgeben? Und auch der genannte Fall muß nicht unsauber sein! Wenn die Funktion z.B. eine Datei liest und die einen Wert zurückliefert willst du dich im Normalfall auf andere Funktionen zurückgreifen die ihrerseits eine Exception auslösen können. Natürlich KÖNNTEST du die jezt in deiner lese Routine verarbeiten... oder aber die Leseroutine geht erstmal davon aus das alles glatt läuft, und wenn nicht, wird der Fehler halt ggf von der aufrufenden Instanz behandelt (da du selber den Fehler vieleicht garnicht behandeln kannst).
Vorsicht, persönliche Meinung. Exceptions sind genauso eine Seuche wie Fließkommazahlen: Jeder Depp will sie haben, in den allermeisten Fällen sind sie total unnötig und niemand macht sich mal die Mühe, zu verstehen, was sie überhaupt können und nicht können. Man kann sie richtig benutzen und so weiter, keine Frage. Allzuoft aber artet das in faule Programmierung aus, so von wegen soll sich doch die nächste Instanz um den Fehler kümmern. Das haut voll gegen Kapselung etc. Prinzipiell gibt es ja nur zwei Möglichkeiten: Entweder, ich vertusche Fehler (durch 'Abhauen') oder ich werfe sie nochmal. Kannst dir ja aussuchen, was besser oder sauberer ist. Einen Backtrace krieg ich im Debugger auch ohne Exceptions hin, gar kein Problem. Mit solchen Fällen wie z.B. 'log(0)' könnte man sich streiten, ob das entweder Aufgabe des FP-Prozessors ist, den Fehler aufzufangen (der hat ja auch ein Fehlerregister, welches man auslesen kann) oder ob es Aufgabe der Anwendung ist, falsche Werte erst garnicht so weit kommen zu lassen. Die Sache mit dem Abbruch an der richtigen Stelle hast du ja selbst schon entdeckt: Erst throw, throw, throw und danach if, if, if, um festzustellen, bis wohin es geklappt hat, um danach sauber aufräumen zu können...
Zu log(0) gibt es 3 Lösungen -Exception -global math_errno einführen -interface ändern => double log(double val, int & err) Aber das war ja nicht die Frage ;) Wer hindert dich denn daran eine Exception Hierarchie aufzubauen? Exception <- MemoryException <- MathException <- InvalidParameterException <- .. dann kannst du try { allocMem(); double d = log(0); string name = getName("223"); } catch(Exception & exp) { exp.what(); exit(1); } Oder habe ich die Frage missverstanden?
> oder aber die Leseroutine geht erstmal davon aus dass alles glatt läuft ... aber wenn nicht, sind die Daten in einem inkonsistenten Status - das macht bei diesem Beispiel jetzt nicht so viel aus, aber wenn man eine Funktion hat, die mit komplexen Datenstrukturen im Speicher arbeitet und optimistisch davon ausgeht dass nichts schiefgeht, aber dann doch ein Fehler auftritt, können die Daten in einem halb-verarbeiteten Zustand sein, der jede weitere Verarbeitung unmöglich macht, was man durch abfangen der Exception auf höherer Ebene auch nicht mehr in den Griff kriegen kann, da die höhere Ebene ja gar nicht genau weiß, welche Operation fehlgeschlagen ist, um die Daten "reparieren" zu können. > und niemand macht sich mal die Mühe, zu verstehen, was sie überhaupt > können und nicht können. Genau deswegen frage ich hier ja :) > Einen Backtrace krieg ich im Debugger auch ohne Exceptions hin, gar kein > Problem. Aber dazu muss das Programm doch beim auftreten des Fehlers unterbrochen werden, damit du einen backtrace kriegst ... ? Wenn aber z.B. eine Funktion a () eine Funktion b () aufruft, in der fopen () aufgerufen wird, was fehlschlägt, sodass b() einen Fehler zurückgibt, sodass a () einen Fehler zurückgibt, dann weiß der Aufrufer von a () wohl, dass ein Fehler aufgetreten ist, aber dann kriegst du im Debugger doch keinen Backtrace? Um einen solchen zu kriegen, muss doch die Ausführung in einer Funktion (wie fopen) stehen bleiben (durch einen Breakpoint oder ein signal, SIGINT oder so), sodass man dann (z.B. in gdb) "bt" eingeben kann. Wenn aber fopen() mit Fehler zurückkehrt, genauso wie b() und a(), hast du doch keinen backtrace, oder übersehe ich da was? > Was willst du als Fehlernummer zurückgeben? > -interface ändern => double log(double val, int & err) Zitat aus der log(x)-Manpage: If x is zero [für nicht-Mathematiker: log(0) geht nicht], then a pole error occurs, and the functions return -HUGE_VAL [für die version mit double], -HUGE_VALF [für float], or -HUGE_VALL [für long double], respectively. If x is negative (including negative infinity) [was auch eine ungültige Eingabe wäre], then a domain error occurs, and a NaN (not a number) is returned. Ist doch gut, man kann ganz eindeutig unterscheiden, wann ein Fehler aufgetreten ist, und wann nicht, und was für ein Fehler es ist. Und das ohne Exceptions...
Naja ich will hier keinen Glaubenskrieg auslösen... was ich angeführt habe waren nur BEISPIELE... und sicher gibt es n+1 Möglichkeiten das anders zu lösen keine Frage. Ich glaube aber du denkst zu sehr in der 'C-Welt' wo man so manche Schweinerei als normalen Programmierstil empfindet ;) Es sit einfach so, ein RÜckgabewert ist ein Rückgabewert, ein Fehler/Exception ist halt was völlig anderes. Wenn du nen konkretes Beispiel/Problem hast könnt ich dir sicher dazu noch was erzählen ansosnten findest du bei Google sicher genug "Streitgespräche" in einschöägigen Foren :)
Niklas G. wrote: >> Einen Backtrace krieg ich im Debugger auch ohne Exceptions hin, gar kein >> Problem. > Aber dazu muss das Programm doch beim auftreten des Fehlers unterbrochen > werden, damit du einen backtrace kriegst ... ? Ist schon richtig, aber überlege mal: Was willst du zur Laufzeit mit einem Backtrace...? >> Was willst du als Fehlernummer zurückgeben? >> -interface ändern => double log(double val, int & err) > Zitat aus der log(x)-Manpage: > If x is zero [für nicht-Mathematiker: log(0) geht nicht], then a pole > error occurs, and the functions return -HUGE_VAL [für die version mit > double], -HUGE_VALF [für float], or -HUGE_VALL [für long double], > respectively. > If x is negative (including negative infinity) [was auch eine ungültige > Eingabe wäre], then a domain error occurs, and a NaN (not a > number) is returned. > Ist doch gut, man kann ganz eindeutig unterscheiden, wann ein Fehler > aufgetreten ist, und wann nicht, und was für ein Fehler es ist. Und das > ohne Exceptions... Und: http://www.gnu.org/software/libc/manual/html_node/Floating-Point-Errors.html#Floating-Point-Errors
> Ist doch gut, man kann ganz eindeutig unterscheiden, wann ein > Fehler aufgetreten ist, und wann nicht, und was für ein Fehler es ist. > Und das ohne Exceptions... Das geht halt nur für Funktionen, wo man spezielle Werte für Fehler reservieren kann. Die sind aber nicht für alle Funktionen gleich bzw. für manche Funktionen gibt es gar keine. Also würde es immer je nach Funktion unterschiedlich gehandhabt werden müssen. Es gibt auch viele Programmierer, die es als schlecht ansehen, wenn Ergebnisse aus einer Funktion mit dem Fehlerhandling vermischt werden.
> Es gibt auch viele Programmierer, die es als schlecht ansehen, wenn > Ergebnisse aus einer Funktion mit dem Fehlerhandling vermischt werden. Das macht Sinn. Man kann aber auch einer Funktion einen Pointer/Referenz übergeben, über den dann das Ergebnis ausgegeben wird, und der Rückgabewert zeigt einen Fehler oder Erfolg an, oder umgekehrt.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.