Error handling: Exception vs. Error return value
-
bgx schrieb:
Du musst auch bedenken das cooky451 noch Student ist und dementsprechend nur an "Mini"-Projekten mitgearbeitet hat. Deswegen beurteilt er alles aus einem anderem, eher naiven, Blickwinkel.
Wer sagt, dass ich das nicht bin?
Und wieso sollte er nur kleine Projekte gesehen haben?Und seine Meinung soll er haben, aber er kann sie mir erklären und seine Beweggründe gleich mit. Ich hoffe nämlich, dass ich das verstehe und dann merke: "Ey, das is ja voll cool. Wenn ich das ab jetzt so mache, krieg ich alle Mädels ab"
-
Code als Beispiel meinte ich eigentlich eher weniger. Nur halt etwas durchdachter. Hier erfüllen beide Programme ja nur genau einen Zweck, also letztlich ist es kaum relevant, wie man den Fehler transportiert. Damit das relevant wird, brauchen wir irgendetwas wo man viel macht. Ein Beispiel wäre z.B. ein (relativ sinnloses) Programm, dass ab einem Pfad alle Dateien durch geht und deren Hashes berechnet. Hier macht man wohl einen Thread, der einfach die Verzeichnisse durchläuft und alle Dateinamen in eine queue pusht. Aus der lesen dann mehrere Threads, öffnen die Dateien, lesen, berechnen ihren Hash, etc. Jetzt kann eine Datei nicht geöffnet werden. Will man an dieser Stelle wirklich viel Code überspringen? (aka eine Exception werfen?) Eher nicht. Man schreibt einfach irgendwo rein "sorry, ging nicht" und macht weiter. Im besten Fall versucht man es später noch mal.
-
Wenn du bei Programmstart die existenz aller benötigten Dateien geprüft hast und irgendein Vollpfosten löscht sie dir danach unterm Hintern weg, dann wirf eine pebkac-Exception und gut is'.
Erstes Beispiel ist irgendwie komisch. In diesem simplen Fall natürlich if/else und gut isses. Anders sieht's aus, wenn nicht nur das Öffnen der Datei fehlschlagen kann, sondern auch zig darauf folgende Operationen. Da gebietet imho schon allein die Ästhetik try/catch.
-
cooky451 schrieb:
Der Punkt ist, dass der eigentliche Grund warum Exceptions geworfen werden immer weniger vor kommt, je größer und zuverlässiger das Programm wird.
Hä? Du programmiertst, daß weniger oft der SPeicher ausgeht, die Platte besser ist und der Benutzer die CD nicht rausnimmt?
cooky451 schrieb:
Man hat ein Mini-Programm geschrieben dass ein Fenster öffnet auf dem drei Buttons sind? Jetzt failt CreateWindow? Wunderbar, Exception + exit. Was soll man auch sonst machen.
Soweit ok.
cooky451 schrieb:
Wenn man jetzt aber ein Programm mit 3 Fenstern und 20 Sub-Fenstern hat bei dem alles asynchron läuft und irgendwo kann da jetzt ein Fenster nicht geöffnet werden (oder eine Verbindung nicht hergestellt werden, was auch immer), ist das wirklich ein Grund große Teile des Codes zu überspringen? Eher nicht, das Programm bei so etwas mal eben weit zurückfallen zu lassen wäre wohl schwachsinn, der ganze Rest funktioniert doch noch.
Sagt ja keiner, daß die Exception erst in der main gefangen wird.
cooky451 schrieb:
Ein großes Argument für Exceptions das ich auch immer wieder lese ist die größere Menge an Fehlerbeschreibung die man da rein bekommt.
Das teile ich nicht.
cooky451 schrieb:
Hey, wenn man das unbedingt braucht: Man muss Exceptions ja nicht werfen!
Siehe z.B. Alexandrescus Expected<T>, durchaus ein interessantes Konzept.
Mal schauen.
cooky451 schrieb:
Noch schöner wäre allerdings, wenn der Compiler erkennen könnte, dass keine Exception geworfen werden kann, wenn man selbst kontrolliert.
Die gehts jetzt aber hoffentlich nicht nur um Performance.
-
cooky451 schrieb:
Skym0sh0 schrieb:
Ok, cooky: Du sagst also, dass durch bessere Programmierung Exceptions unnötiger werden, weil sie einfach nicht gebraucht werden?
Nein. Ich sage dass die Fälle in denen Exceptions sinnvoll sind, einfach immer weniger werden je größer das Programm wird.
*meld* Das Muster kenne ich:
Du mit Deinen kleinen Programmen kannst ja schreiben, wie Du willst. Aber echte Entwickler, diue echt große Programme schreiben, verwenden natürlich UML. Wer das nicht tut, der ist ein kleiner Zwerg.Ebenso muß man UN benutzen, sobald das Programm mal mehr als 20 Mannjahre braucht. Wer UN nicht benutzt, der hat null Ahnung.
Nur mit Struktogrammen kann man einigermaßen fehlerfrei planen. Wobei fehlerfreie Programme gibt es eh nicht, das ist mathematisch bewiesen.
Da verweise ich dann aber auf Fachliteratur: http://de.wikipedia.org/wiki/Des_Kaisers_neue_Kleider
-
cooky451 schrieb:
Sockets. Ein Server der gerade mit 10k Clienten verbunden ist, will nicht wirklich viel mehr als einen log-Eintrag machen wenn er nicht noch einen Socket erstellen kann. Wenn ein send/recv fehl schlägt vielleicht noch nicht einmal das.
Jo, aber wenn der Socket nicht erstellt werden kann, dann hab ichj zwei Möglichkeiten: Untebleiben und noch ein paarmal probieren, oder hochgehen und oben einfach weitermachen, der nächste CLient wird schon wieder einen Socket anlegen können. Wie weit hoch gehen? Ist mir unten völlig egal!!! Das erledigen Exceptions auf feinste Weise.
-
cooky451 schrieb:
Ja, aber du willst nur ne Fehlermeldung anzeigen und nicht wirklich viel Code im gleichen oder höheren Scope überspringen.
Falsch!
Der Benutzer hat "Connect" gedrückt. Ich mache Sockets auf, lese Config-Files, dabei steig ich doch schon drei Funktionslevel runter oder mehr. Und dann klappt was nicht. Also mache ich kein normales Fenster auf, sondern zeige ihm einen Fehlerdialog. Und zwar, indem die Exception ungefähr in der Höhe gefangen wird, wo der Benutzer den "Connect"-Knopf gedrückt hat.
Und? Die anderen 49 Sitzungsfenster bleiben davon doch unberührt und laufen normal weiter. Ob der DIalog modal angezeigt werden soll oder nicht? Hast freie Auswahl.
-
Das heisst, so in etwa würde das aussehen, Pseudocodemäßig
vector<Hash> hashings; void calcHash(Path p) { File f; f.open(p); Hash h = calcHashImpl(f); hashings.push_back(h); } int main() { Path path = getPath(); Threads = createThreads(5); Queue q; Thread[0].run( traversePathAndPushBack(path, q) ); while ( !q.empty() ) { Thread t = getNextFreeThread(Threads); t.run( calcHash(q.next()) ); } return 0; }
Dann wäre das hier in etwa deine Version:
vector<Hash> hashings; void calcHash(Path p) { File f; f.open(p); Hash h = calcHashImpl(f); if ( !h.failed() ) hashings.push_back(h); } int main() { Path path = getPath(); Threads = createThreads(5); Queue q; Thread[0].run( traversePathAndPushBack(path, q) ); while ( !q.empty() ) { Thread t = getNextFreeThread(Threads); t.run( calcHash(q.next()) ); } return 0; }
Und das hier die Version mit Exceptions:
vector<Hash> hashings; void calcHash(Path p) { File f; f.open(p); try { Hash h = calcHashImpl(f); hashings.push_back(h); } catch ( ... ) { cerr << "geht nicht" << endl; } } int main() { Path path = getPath(); Threads = createThreads(5); Queue q; Thread[0].run( traversePathAndPushBack(path, q) ); while ( !q.empty() ) { Thread t = getNextFreeThread(Threads); t.run( calcHash(q.next()) ); } return 0; }
Huch, auf einmal mischen sich noch Menschen hier ein ?! Oo
Die Hälfte von volkards Posts verstehe ich wieder nicht
Und Sarkasmus/Ironie kommt im Interwebz meistens scheisse, weil mans nicht unbedingt versteht
-
Was dem Linuxer seine Distribution ist dem C++ler seine exception!
In meinem aktuellen Projekt, das nur so vor Benutzer-abhängigen Dingen (Nein, keine Pille-Palle-Stringfelder die man mal so fix auf validität prüft) strotzt, wüsste ich nicht, wie ich ohne exceptions Herr der Lage würde...
-
volkard schrieb:
cooky451 schrieb:
Skym0sh0 schrieb:
Ok, cooky: Du sagst also, dass durch bessere Programmierung Exceptions unnötiger werden, weil sie einfach nicht gebraucht werden?
Nein. Ich sage dass die Fälle in denen Exceptions sinnvoll sind, einfach immer weniger werden je größer das Programm wird.
*meld* Das Muster kenne ich:
Du mit Deinen kleinen Programmen kannst ja schreiben, wie Du willst. Aber echte Entwickler, diue echt große Programme schreiben, verwenden natürlich UML. Wer das nicht tut, der ist ein kleiner Zwerg.Ebenso muß man UN benutzen, sobald das Programm mal mehr als 20 Mannjahre braucht. Wer UN nicht benutzt, der hat null Ahnung.
Nur mit Struktogrammen kann man einigermaßen fehlerfrei planen. Wobei fehlerfreie Programme gibt es eh nicht, das ist mathematisch bewiesen.
Da verweise ich dann aber auf Fachliteratur: http://de.wikipedia.org/wiki/Des_Kaisers_neue_Kleider
Ok, willst du damit sagen, dass cooky nur die Augen vershcließt vor dem Offensichtlichen?
Nämlich, dass auf jedenfall irgendwo etwas schiefgehen kann und damit alles zerschiest?
-
Skym0sh0 schrieb:
volkard schrieb:
cooky451 schrieb:
Skym0sh0 schrieb:
Ok, cooky: Du sagst also, dass durch bessere Programmierung Exceptions unnötiger werden, weil sie einfach nicht gebraucht werden?
Nein. Ich sage dass die Fälle in denen Exceptions sinnvoll sind, einfach immer weniger werden je größer das Programm wird.
*meld* Das Muster kenne ich:
Du mit Deinen kleinen Programmen kannst ja schreiben, wie Du willst. Aber echte Entwickler, diue echt große Programme schreiben, verwenden natürlich UML. Wer das nicht tut, der ist ein kleiner Zwerg.Ebenso muß man UN benutzen, sobald das Programm mal mehr als 20 Mannjahre braucht. Wer UN nicht benutzt, der hat null Ahnung.
Nur mit Struktogrammen kann man einigermaßen fehlerfrei planen. Wobei fehlerfreie Programme gibt es eh nicht, das ist mathematisch bewiesen.
Da verweise ich dann aber auf Fachliteratur: http://de.wikipedia.org/wiki/Des_Kaisers_neue_Kleider
Ok, willst du damit sagen, dass cooky nur die Augen vershcließt vor dem Offensichtlichen?
Nämlich, dass auf jedenfall irgendwo etwas schiefgehen kann und damit alles zerschiest?Nein. Ich will nur hiermit das Programmgrößenargument entwerten und weg haben.
-
Ich wüsste gerne einmal die Argumentation, die zu der Aussage führte...
-
Wenn Fehler nicht Teil des normalen Programmablaufs sind, nehm ich immer Exceptions/Asserts.
Exceptions sind deutlich eleganter, sicherer, auf vielen Rechnern performanter als Fehlercodes oä. als Rückgabewert.
-
Ethon schrieb:
auf vielen Rechnern performanter als Fehlercodes oä. als Rückgabewert.
Woher nimmst du die Info? Und vor allem, warum ist das so?
Ich dachte immer, Exceptions sind lahme Krücken.
-
theliquidwave schrieb:
Ethon schrieb:
auf vielen Rechnern performanter als Fehlercodes oä. als Rückgabewert.
Woher nimmst du die Info? Und vor allem, warum ist das so?
Ich dachte immer, Exceptions sind lahme Krücken.Auf x64-CPUs lässt sich Exception-Handling komplett kostenlos implementieren, WENN die Exception nicht fliegt. Wenn sie fliegt ist es teurer.
Aber da Fehler generell Ausnahmen und nicht Regelfall sind, kann man davon ausgehen dass es performanter ist. (Hab ja gesagt dass ich Return-Werte nutzen würde, wenn Fehler zum normalen Programmablauf gehören)
-
Wenn Fehler zum normalen Programmablauf zählen, dann handelt es sich ja eigentlich nicht um Fehler, oder? Dann sind es vielleicht Ereignisse oder Zustände^^
Z.b. halte ich eine IncompetentUserException für fragwürdig, wobei eine CompetentUserException schon nachvollziehbar ist!
-
Hier ein etwas anderer Ansatz: http://isocpp.org/blog/2012/12/systematic-error-handling-in-c-andrei-alexandrescu . Exceptions werden mit dem Wert verknueft. Sowas Wie Maybe/Either in Haskell, wie boost.optional nur mit Exceptions. Exceptions werden geworfen, wenn das Ergebnis benutzt wird. Wenn keine Exception geworfen werden soll, dann kann vorher mittels valid das Ergebnis geprueft werden. Der zweite Teil beschaeftigt sich mit ScopeGuards und wie im Fehlerfall elegant aufgeraemt werden kann.
-
Ethon schrieb:
Auf x64-CPUs lässt sich Exception-Handling komplett kostenlos implementieren
Ist das auch bei 32bit Prozessen so, die auf einem x64_86 System laufen? (Windows 8 64bit zun Beispiel)
-
theliquidwave schrieb:
Ethon schrieb:
Auf x64-CPUs lässt sich Exception-Handling komplett kostenlos implementieren
Ist das auch bei 32bit Prozessen so, die auf einem x64_86 System laufen? (Windows 8 64bit zun Beispiel)
Nein, ein 32bit Prozess ist ein 32bit Prozess.
Aber es ist nicht so als würde Exception Handling so teuer sein.
Du kannst dir das hier mal den ersten von meinem Artikel hier durch lesen:
http://www.c-plusplus.net/forum/219865
-
cooky451 schrieb:
Siehe z.B. Alexandrescus Expected<T>, durchaus ein interessantes Konzept.
Ich bin gerade dabei mir seinen Vortrag über genau das anzusehen, aber ich habe ein Problem mit dem Verstehen des Konstruktors.
template <class T> class Expected { union { T ham; std::exception_ptr spam; }; bool gotHam; // ... Expected(const Expected& rhs) : gotHam(rhs.gotHam) { if (gotHam) new(&ham) T(rhs.ham); // ^~~~~~~~~~~~~~~~~~~~~ else new(&spam) std::exception_ptr(rhs.spam); // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // was bedeutet das? } // ... };
Ich weiß, dass man den new-operator mit mehreren Parametern überladen kann, allerdings finde ich keine Überladung mit bool, geschweige denn mit T
Ich habe das jetzt so verstanden, dass (if-block:) ham mit einer kopie von rhs.ham bzw. (else-Block:) spam mit einer kopie von rhs.spam initialisiert wird