Wann exceptions werfen?



  • Naja, Java wirft doch bei allem sofort.

    Nullpointer-Exception, IOException, FileNotFoundException, AssertionError, OutOfMemoryError, etc...



  • sebi707 schrieb:

    Mit Fehlercodes ist es doch noch viel ungünstiger, weil man jede Funktion separat checken muss und den Fehler an die nächst höhere Funktion weitergeben. Der Vorteil von Exceptions ist ja, dass diese automatisch zu den nächst höheren Funktionen weiter gegeben werden bis ein try/catch Block gefunden wird.

    Ich versuche mal mein Problem besser zu umschreiben. Folgender code:

    // ... sonstiger code ...
    Kopierfunktion_1(dest1, source1);
    Kopierfunktion_2(dest2, source2);
    Kopierfunktion_3(dest3, source3);
    // ... sonstiger code ...
    

    Diese beispielhaften Kopierfunktionen werden in einer Methode hintereinander aufgerufen. Jede Funktion kann exceptions werfen, wenn z.B. kein Speicher alloziert werden kann oder der gegebene Parameter 'source' leer ist. Alle Funktionen sind unabhängig und dürfen Fehlschlagen, wenn der 'source'-Parameter leer ist. Um mögliche exceptions abzufangen, könnte man nun folgendes machen:

    // ... sonstiger code ...
    try
    {
    	Kopierfunktion_1(dest1, source1);
    	Kopierfunktion_2(dest2, source2);
    	Kopierfunktion_3(dest3, source3);
    }
    catch(const MyException &e)
    {
    	int errorCode = e.getError();
    
    	if (errorCode != e.errorCodes::NO_DATA_AVAILABLE) // Fehler ignorieren, wenn keine Daten zum kopieren vorhanden sind.  ...ansonsten werfen.
    	{
    		throw;
    	}
    }
    // ... sonstiger code ...
    

    Der Catch-Block prüft, ob es ein kritischer Fehler ist und wirft ggf. eine exception. Sind nur keine Daten zum kopieren vorhanden, so wird der Fehler ignoriert. Problem ist, wenn die erste Kopierfunktion fehlschlägt, dann kommen die anderen beiden Zeilen nicht mehr zur Ausführung. Um das zu umgehen, könnte man nun das tun:

    // ... sonstiger code ...
    try
    {
    	Kopierfunktion_1(dest1, source1);
    }
    catch(const MyException &e)
    {
    	int errorCode = e.getError();
    
    	if (errorCode != e.errorCodes::NO_DATA_AVAILABLE) // Fehler ignorieren, wenn keine Daten zum kopieren vorhanden sind.  ...ansonsten werfen.
    	{
    		throw;
    	}
    }
    
    try
    {
    	Kopierfunktion_2(dest2, source2);
    }
    catch(const MyException &e)
    {
    	int errorCode = e.getError();
    
    	if (errorCode != e.errorCodes::NO_DATA_AVAILABLE) // Fehler ignorieren, wenn keine Daten zum kopieren vorhanden sind.  ...ansonsten werfen.
    	{
    		throw;
    	}
    }
    
    try
    {
    	Kopierfunktion_3(dest3, source3);
    }
    catch(const MyException &e)
    {
    	int errorCode = e.getError();
    
    	if (errorCode != e.errorCodes::NO_DATA_AVAILABLE) // Fehler ignorieren, wenn keine Daten zum kopieren vorhanden sind.  ...ansonsten werfen.
    	{
    		throw;
    	}
    }
    // ... sonstiger code ...
    

    Ja... das geht, aber das sieht irgendwie schrecklich aus. Würde ich Fehlercodes für unkritische Fehler und exceptions für kritische Fehler nehmen, dann sieht es schon besser aus:

    try
    {
    	// ... sonstiger code ...
    	if (Kopierfunktion_1(dest1, source1) == -1)
    	{
    		// unkritischer Fehler: naja nichts schlimmes und ggf. loggen (z.B. source ist leer und hat nichts zum Kopieren). Fatale Fehler würden Exceptions werfen und keinen Fehlercode zurückgeben
    	}
    	if (Kopierfunktion_2(dest2, source2) == -1)
    	{
    		//...
    	}
    	if (Kopierfunktion_3(dest3, source3) == -1)
    	{
    		//...
    	}
    	// ... sonstiger code ...
    }
    catch(const MyException &e)
    {
    	// irgendein kritischer Fehler
    }
    

    ist die letzte Variante ein schlechter Programmierstil? Ich lese immer wieder in Foren oder Tutorials, dass man Exceptions nur für Ausnahmen verwenden soll. Das halte ich auch für sinnvoll. Nur ist die frage, was man bei banalen (informativen) Fehlern/Warnungen macht. Wenn man hier keine Exception werfen sollte, muss es einen anderen Weg geben diese Fehler (optional) zu behandeln oder zu Prüfen.

    ....gut in meinem Beispiel könnte ich eine Methode schreiben (z.B. isSourceOccupied(source)), die vor dem Kopieren erstmal prüft, ob 'source' Daten enthält und einen bool zurückgibt, aber man muss es sich ja nicht unnötig komplizierter machen. ...bzw möglicherweise gibt es Fälle, in denem man das nicht so einfach prüfen kann.

    Was würdet Ihr machen? Eines von meinen Beispielcodes anwenden oder doch was ganz anderes?



  • Skym0sh0 schrieb:

    Wieso wird diese Frage eigentlich nicht wie in Java beantwortet?
    Oder umgekehrt, wieso macht man es in Java einfach so und wirft bei jeder Kleinigkeit eine Exception?

    Weil Java ursprünglich für ganz andere Ziele gemacht wurde: Damit extrem schlechte unausgebildete Progger für embedded systems extrem schlechte billige Programme schreiben. Türlich baut man dafür einen GC ein und versucht, Programmierfehler zu fangen.

    Skym0sh0 schrieb:

    Und vor allem soll kein Flamewar oder Shitstorm gestartet werden)

    Wenn Du die Antwort nicht verträgst, dann frag nicht. Man kann Java nicht ohne dessen Geschichte verstehen. https://en.wikipedia.org/wiki/Oak_(programming_language)



  • Im gezeigen Beispiel macht eine lokale Behandlung der Fehler ja auch Sinn (zumindest falls man mit nur einem Teil der Kopierten Dateien was anfangen kann). Da wäre ich auch eher für Rückgabewerte. Man rechnet bei sowas wie Datei kopieren ja schon fast mit Fehlern. fstream::open wirft auch keine Exception wenn es die Datei nicht öffnen kann.



  • es ist scheinbar nicht so trivial ein geeignetes Mittelmaß zu finden.



  • SBond schrieb:

    es ist scheinbar nicht so trivial ein geeignetes Mittelmaß zu finden.

    Das ist eben, wie ich finde, der Punkt. Die einen sagen "Keine Exceptions in C++ nutzen, weil langsam, weil unübersichtlich, weil Fehleranfällig, weil weil weil..." und die anderen sagen "Für alles Exceptions nutzen und werfen".

    Und dann sind da Praxisbeispiele, wo manchmal was geworfen wird und manchmal nicht.
    Dafür werden dann Assertions im Programm eingebaut, wo dann hier und da mal (im Production Betrieb ?!) das Programm einfach aussteigt ohne Fehlermeldung, ohne Stacktrace ohne alles.

    Ich blick da nicht mehr durch 😞
    Muss ich wohl zu Java rübergehen... :|



  • SBond schrieb:

    es ist scheinbar nicht so trivial ein geeignetes Mittelmaß zu finden.

    Das Video mit Expected<T> hast du gesehen? Ich habe es selbst noch nicht benutzt aber ich finde es sehr praktisch, dass dort beide Ansätze vereint werden. Wenn man möchte nutzt man den Rückgabewert und behandelt den Fehler lokal. Wenn nicht, dann wirft man eben eine Exception und lässt den Fehler zu höheren Funktionen propagieren. Vermutlich kann man die Idee auch für Funktionen ohne Rückgabewert, wie in deinem Beispiel, erweitern.



  • Skym0sh0 schrieb:

    Wieso wird diese Frage eigentlich nicht wie in Java beantwortet?
    Oder umgekehrt, wieso macht man es in Java einfach so und wirft bei jeder Kleinigkeit eine Exception?

    (Das ist keine Wertung für/gegen Java/C++, sondern eifnach eine Frage! Und vor allem soll kein Flamewar oder Shitstorm gestartet werden)

    Weil in C++ schnell alles explodiert mit RAII und Exceptions. Praktisch darf nichts was im Destruktor an Aufräumarbeiten gemacht wird eine Exception aus dem Destruktor werfen, weil sonst im Falle einer anderen Exception alles crasht.



  • destruktör schrieb:

    Weil in C++ schnell alles explodiert mit RAII und Exceptions. Praktisch darf nichts was im Destruktor an Aufräumarbeiten gemacht wird eine Exception aus dem Destruktor werfen, weil sonst im Falle einer anderen Exception alles crasht.

    Was passiert in Java, wenn eine Exception geworfen wird und deswegen irgendwann später vom GC ein Objekt vielleicht zerstört wird, das eine Schreibdatei offen hat? Was soll der Destruktor des Objektes machen, wenn das zerstören der Datei nicht die letzten paar Bytes rausschreiben kann? Nix natürlich, wobei wir wieder bei meiner These sind.



  • Exception im Destruktor werfen ist eh ne ganz blöde Sache.
    Bei Sachen wie Dateifunktionen ist es sicher nicht verkehrt, mal ein false zurückzugeben, wenn die zu kopierende Datei nicht gefunden wurde oder das Ziel bereits existiert. Wenn allerdings ein anderer Fehler auftritt, ist eine Ausnahme angebracht.



  • sebi707 schrieb:

    Das Video mit Expected<T> hast du gesehen? Ich habe es selbst noch nicht benutzt aber ich finde es sehr praktisch, dass dort beide Ansätze vereint werden.

    Ja habe ich 🙂
    Ich werde mir die Tage überlegen ob ich das so machen werde. Über Weihnachten wird woch nicht so viel passieren.

    ...wünsche euch allen noch schöne Feiertage 🙂



  • volkard schrieb:

    destruktör schrieb:

    Weil in C++ schnell alles explodiert mit RAII und Exceptions. Praktisch darf nichts was im Destruktor an Aufräumarbeiten gemacht wird eine Exception aus dem Destruktor werfen, weil sonst im Falle einer anderen Exception alles crasht.

    Was passiert in Java, wenn eine Exception geworfen wird und deswegen irgendwann später vom GC ein Objekt vielleicht zerstört wird, das eine Schreibdatei offen hat? Was soll der Destruktor des Objektes machen, wenn das zerstören der Datei nicht die letzten paar Bytes rausschreiben kann? Nix natürlich, wobei wir wieder bei meiner These sind.

    Ich hab schon ewig nichts mehr mit Java gemacht, seit wann gibt es da Destruktoren?


  • Mod

    destruktör schrieb:

    volkard schrieb:

    destruktör schrieb:

    Weil in C++ schnell alles explodiert mit RAII und Exceptions. Praktisch darf nichts was im Destruktor an Aufräumarbeiten gemacht wird eine Exception aus dem Destruktor werfen, weil sonst im Falle einer anderen Exception alles crasht.

    Was passiert in Java, wenn eine Exception geworfen wird und deswegen irgendwann später vom GC ein Objekt vielleicht zerstört wird, das eine Schreibdatei offen hat? Was soll der Destruktor des Objektes machen, wenn das zerstören der Datei nicht die letzten paar Bytes rausschreiben kann? Nix natürlich, wobei wir wieder bei meiner These sind.

    Ich hab schon ewig nichts mehr mit Java gemacht, seit wann gibt es da Destruktoren?

    Er meint (vermutlich) die Finalize-Methoden, die so etwas ähnliches wie ein Destruktor in Java sind. Kommt halt drauf an, wie man das Konzept eines Destruktors genau definiert. Mit dem Sprachkonzept der deterministischen Destruktoren in C++ (welches meiner Meinung nach ein sehr starkes Konzept ist, und einer der Hauptvorzüge von C++*) haben die Finalizers meiner Meinung nach nur oberflächlich zu tun.
    Was dann auch volkards Aussage erklärt.

    *: Um so mehr ist es schade, dass viele schlechte Lehrer und Lehrbücher sich dessen überhaupt nicht bewusst sind.



  • Das einzige, was dem C++-Destruktor bei Exceptions in Java halbwegs nahekommt, war früher die umständliche finally-Anweisung und seit Java 7 die try-with-resources-Anweisung, wobei das Interface closeable implementiert sein muss.



  • bonner ultra schrieb:

    Das einzige, was dem C++-Destruktor bei Exceptions in Java halbwegs nahekommt, war früher die umständliche finally-Anweisung und seit Java 7 die try-with-resources-Anweisung, wobei das Interface closeable implementiert sein muss.

    Dabei wiederum ist es zulässig und vollkommen legal und wohldefiniert, Exceptions zu werfen wie man gerade Lust hat. Egal ob eine oder mehrere, alles passiert vorhersehbbar.



  • SeppJ schrieb:

    destruktör schrieb:

    volkard schrieb:

    destruktör schrieb:

    Weil in C++ schnell alles explodiert mit RAII und Exceptions. Praktisch darf nichts was im Destruktor an Aufräumarbeiten gemacht wird eine Exception aus dem Destruktor werfen, weil sonst im Falle einer anderen Exception alles crasht.

    Was passiert in Java, wenn eine Exception geworfen wird und deswegen irgendwann später vom GC ein Objekt vielleicht zerstört wird, das eine Schreibdatei offen hat? Was soll der Destruktor des Objektes machen, wenn das zerstören der Datei nicht die letzten paar Bytes rausschreiben kann? Nix natürlich, wobei wir wieder bei meiner These sind.

    Ich hab schon ewig nichts mehr mit Java gemacht, seit wann gibt es da Destruktoren?

    Er meint (vermutlich) die Finalize-Methoden, die so etwas ähnliches wie ein Destruktor in Java sind. Kommt halt drauf an, wie man das Konzept eines Destruktors genau definiert. Mit dem Sprachkonzept der deterministischen Destruktoren in C++ (welches meiner Meinung nach ein sehr starkes Konzept ist, und einer der Hauptvorzüge von C++*) haben die Finalizers meiner Meinung nach nur oberflächlich zu tun.
    Was dann auch volkards Aussage erklärt.

    Was genau ist seine Aussage? Soll man in Java wie in C++ programmieren? Wer verwendet diese finalize-Methoden?


Anmelden zum Antworten