Forum: PC-Programmierung Frage zu Exceptions


von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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"?

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

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).

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

> 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 :)

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

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).

von Sven P. (Gast)


Lesenswert?

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...

von daniel (Gast)


Lesenswert?

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?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

> 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...

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

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 :)

von Sven P. (Gast)


Lesenswert?

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

von Rolf Magnus (Gast)


Lesenswert?

> 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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

> 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
Noch kein Account? Hier anmelden.