fat-lobyte hat geschrieben:Du kannst übrigens auch Exceptions von typ bool werfen, wenns dir Spaß macht - nur so nebenbei. sqrt() ist wirklich ein ziemlich schlechtes Beispiel, da in solchen Fällen (zeitkritische Funktionen) ein assert() nicht schaden kann.
Hier wird es witzlos, natürlich kann ich bool werfen, ich kann auch einfach eine Funktion doSomethingUseless() aufrufen und einfach return false; zurückgeben.
sqrt() habe ich mir nicht ausgesucht, sie wurde hier angeführt, um die Vorteile von Exceptions aufzuzeigen. sqrt() ist immer ein schlechtes Beispiel, wenn sich herausstellt, dass es nunmal nicht taugt, um Exceptions zu propagieren.
Ich kann gute Beispiele für Exceptions konstruieren, aber ich habe noch nie jemanden gesehen, der ein gutes Beispiel konstruierte und gleichzeitig für Exceptions war. Wer gute Beispiele konstruiert, weiß, dass sie Exceptions dafür nicht verwendet werden.
Ein Programm, dass über mehr als 5 Ausnahmen verfügt missbraucht Exceptions. Und eigentlich denke ich, dass 2 schon über die obere Grenze hinausgeht.
fat-lobyte hat geschrieben:Xin hat geschrieben:Der Aufwand ein E_SOMETHING zu deklarieren ist wesentlich geringer als für alles eine eigene Exception zu erzeugen.
Der aufwand in jeder Funktion die Fehlschlagen kann einen Zeiger oder Referenz als Argument zu übergeben, weil der Rückgabewert nicht verwendet werden kann ist wesentlich höher als für eine Art von Fehler einen Typen zu deklarieren und ihn dann mehrmals zu verwenden.
Hier kommen wir in den Bereich der Meinungen rein. Ich sehe daraus, dass Du eben nicht für jeden Fehler einen Typ konstruierst, sondern Typen viel häufiger wiederverwendest, als Du tun dürftest, um die Sache sauber zu machen.
fat-lobyte hat geschrieben:Xin hat geschrieben:Die Leute sind wesentlich eher bereit, eine Zeile mit einem 'const int' zu definieren als eine neue Klasse.
Wer sind "Die Leute"? Sprichst du für dich, hast du recht. Ich zum Beispiel habe kein Problem eine Klasse zu definieren, um sie dann als Exception zu verwenden.
Code: Alles auswählen
struct FileError
{
enum {FILENOTFOUND = 0} reason;
std::string msg;
}
Das war nicht allzu lang oder?
Ich habe Wertetabellen, die sind so groß, dass ich 'static unsigned int const' als SUIC definiere, weil es mir zu lang ist 'static unsigned int const' zu schreiben.
Jetzt mach mal 2000 dieser Strukturen davon. Viel Spaß.
'Die Leute' sind die Programmierer, die mir diesbezüglich aufgefallen sind, das ist natürlich keine Verallgemeinerung auf alle, eventuell habe ich auch nur die gesehen, die dazu keinen Bock hatten oder einfach dafür kein Geld bekamen.
Dein Enum zeigt übrigens, dass Du wieder beliebig viele "FileErrors" in einer catch-Anweisung erschlägst. Du hast also in Deinem Catch-Block die Frage zu beantworten, was eigentlich passiert ist und wer den Fehler geworfen hat. Aus einem einfachen Programmfluss entwickelt sich in der Fehlerbehandlung ein zweidimensionales Problem in einem catch Block. Geilomat.
fat-lobyte hat geschrieben:Xin hat geschrieben:Nix für ungut, aber das sind die Realitäten, wenn es eben nicht nur ein Übungsprogramm ist.
Wenn das so ist, dann ist das einfach nur ein Programmierfehler. Wenn sie irgend eine Exception finden und sie "umfrickeln", so dass es für sie passt ist das dann das Problem des Entwicklers. Ein Sprachfeature kann nicht den Programmierer ersetzen, der es auch korrekt verwenden muss. Allerdings muss man bei Fehlerbehandlung (in welcher form auch immer) konsequent bleiben, sonst hat man eben die "Frickelei".
Exceptions fördern diese Frickelei.
Wenn alle Entwickler vorbildlich programmieren würden, müsste keine Software debuggt werden. Ein Großteil von Algorithmen ist Fehlerbehandlung => ein Großteil der Programmierfehler findet sich in der Fehlerbehandlung.
fat-lobyte hat geschrieben:Das mit dem Catch block wo dann alles landet kannst du dir Äquivalent zu einer Fehlervariable die "FILE_OPEN_ERROR" vorstellen. Wenn der programmierer es nicht für nötig hält, den Fehler genauer zu spezifizieren (FILE_NOT_FOUND, PERMISSION_DENIED...), musst du den Fehler auch "herausfrickeln".
Wenn ich sauber arbeite, muss ich keinen Fehler generieren, denn ich weiß an Ort und Stelle, was schief gegangen ist.
fat-lobyte hat geschrieben:Wenn das so ist, dann sollten sich die Programmierer mal überlegen nicht von "irgendwelchen oberklassen" abzuleiten, sondern eine Klassenhierarchie aufzubauen, die der "Fehlerhierarchie" entspricht.
Der Traum vom Software vom Reißbrett.
Der wird in der Softwaretechnik häufiger geträumt.
Eine Realität in die ich nun eintreten darf ist folgende: 20 Jahre Sourcecodes, verschiedene Sprachen, verschiedenene Entwickler und entsprechend unterschiedliche Fähigkeiten. Die meisten sind keine Informatiker.
Die Realität, die ich kürzlich noch hatte: 7 Jahre Sourcecode, verschiedene Sprachen, sehr viel mit Java-Exceptions, nichts passt zusammen. Die Entwickler wissen es, aber tun kann man daran nix mehr. Weder ist der Fehlerverlauf brauchbar nachvollziehbar, noch behandelbar. Eine Fehlerbehandlung in dem Sinne findet selten statt, so dass auf Fehler korrigierend eingewirkt wird => es läuft oder es knallt.
Solange Progammierung ein Hobby ist, kann man propagieren, was das Ideal ist und darf vom idealen Entwickler ausgehen. Als professioneller Entwickler ist es mein Job zu berücksichtigen, dass Entwicklung nicht professionel ist. Entwicklung ist nicht ausleben der Kunst, ein perfektes Programm zu schreiben, sondern ein Problem bezahlbar zu lösen. Und damit dafür brauche ich absolut alles, was mir eine Sprache anbieten kann, um den Zustand der Software zu kontrollieren.
Exceptions bieten mir eine Möglichkeit, Informationen über den Zustand der Software zu vernichten.
fat-lobyte hat geschrieben:Wer ist "keiner"? Ich weiß ja nicht wie du das machst, aber wenn ich mit einer Bibliothek Programmiere, oder den Code von anderen Leuten verwende, dann habe ich die referenz stehts daneben liegen, und sehe was für Exceptions die Funktion wirft.
Hahaha... Du hast eine Referenz...
In der Regel hast Du einen Sourcecode und der macht irgendwas und Dein Job ist, dass er jetzt noch irgendwas anderes macht. Irgendwie. Möglichst in 2 Stunden.
fat-lobyte hat geschrieben:Eine Krücke für _welches_ Sprachfeature? Ein new- das den Programmierer bevormundet, wann das Objekt gelöscht werden soll? Oder müsste es etwas geben, was dem Programmierer erlaubt zu bestimmen, dass das Objekt gelöscht wird, wenn es "out of scope" geht? Hey, moment mal, das nennt sich shared_ptr, und ist ab gcc 4.0 oder Visual Studio 9.1 (oder so) im namespace std::tr1 drinnen, und ab 2009 teil der Standardbibliothek.
Damit wäre die Krücke nämlich das Sprachfeature selbst, also eigentlich gar keine Krücke mehr, sondern eine Lösung.
AutoPointer sind schon gut und richtig, aber indem man die Libs erweitert, stärkt man die Sprache nicht.
fat-lobyte hat geschrieben:Man sieht, anscheinend hast du noch nie mit Exceptions in C++ gearbeitet, denn in C++ gibt es keine "finally" Blöcke. Deren Zweck wird von Destruktoren erfüllt.
Vielleicht solltest du den Exceptions mal eine Chance geben, denn anscheinend kennst du Exceptions nur von Java. Wenn das wirklich so ist, dann kann ich deine Abneigung schon fast verstehen, denn dort kriegt man von jeder Funktion aus der JavaAPI eine um die Ohren geworfen. Ich kann dir versichern: in C++ und mit der Standardbibliothek wirst du zu nichts gezwungen, du hast immer die Wahl zwischen Fehlerbehandlung mit Exceptions und klassischer Fehlerbehandlung.
Ich habe schon länger nicht mehr mit Exceptions gearbeitet, warum sollte ich mit einem Feature arbeiten, dass ich für eine Fehlentwicklung halte. Ein Algorithmus ist nur dann schön, wenn er keine Ausnahmen kennt, das ist meine Maxime.
Ich schreibe einen Compiler, hier gibt es bisher keine Ausnahmen, ausschließlich die Regel. Es ist nicht unwahrscheinlich, dass es eine Exception geben wird, die den Compiler vollständig abbricht, wenn der Aufbau des Compilerbaums nicht möglich ist.
Eine Ausnahme.
Zwischen finally und Destruktor sehe ich massive Unterschiede. Nehmen wir an, Du musst ein FILE * schließen, wie machst Du das?
Einen Stream nehmen? Sorry, die Lib, die Du verwendest frisst nur FILE*. AutoPtr? Der ruft delete, nicht aber fclose(). Neues Objekt erstellen, dass im Falle eines Fehlers im Destruktor fclose() ruft.
Das ganze nochmal für CloseLibrary(). Und free(), weil die Lib Dir nunmal einen Pointer auf einen mit malloc()-generierten Datensatz liefert. Und unlock(), weil sonst die Library später nicht mehr verwendet werden könnte.
Jede Wette: 90% derartiger Probleme sind falsch gelöst.
Zum Glück ist der Fehlerfall nicht so häufig, dass während des Programmablaufs die File-Handles ausgehen. Und wenn sowas wie unlock() schief geht... gibt's jemanden, der sich darüber wundert, wenn er Windows neustarten muss, damit irgendwas läuft?
fat-lobyte hat geschrieben:Xin hat geschrieben:Nochmals: Ich bin nicht gegen Exceptions, aber ich bin absolut gegen die Verwendung von Exceptions als Regel. Dass Dateien nicht gefunden werden ist keine Ausnahme.
Ich bin dagegen, für alle Probleme vordefinierte Regeln und Lösungen zu verwenden. Wenn mir die Situation vorteilhaft erscheint, verwende ich exceptions, ansonsten nicht. Das gilt übrigens für alle Sprachfeatures: Ich finde man sollte tief in den Sack mit Möglichkeiten greifen, und die passendste rausholen, anstatt an Binsenweisheiten festzuhalten.
Das sehe ich ähnlich, nur greife ich in Schlussfolgerung dazu sehr selten zu Exceptions. In C++ derzeit in keinem aktiven Projekt.
----------------
Kerli hat geschrieben:Nein es ist nur ein try{} catch nötig in dem der ganze benötigte Speicher angfordert wird.
Keine Ahnung, was Du für Algorithmen schreibst, aber meine Algorithmen brauchen am Anfang speicher, wissen aber noch nicht, ob sie in der Mitte nochmal Speicher brauchen. Ich kann am Anfang keine Liste aufführen, und alles in einen Try-Block packen, ich brauche dafür mehrere oder ich fordere reichlich Speicher an, von dem ich die Hälfte nie brauche.
Beispiele konstruieren ist schön, aber sie sollten schon überzeugen.
Kerli hat geschrieben:Xin hat geschrieben:Kerli hat geschrieben:Und was machst du eigentlich wenn 'run' fehlschlägt?
Eben... die Frage stelle ich Dir ja auch andauernd und trotzdem baust Du hier noch eine Lösung zusammen, die genau diese Frage auch nicht beantwortet. Schade eigentlich.
Warum, die Lösung ist doch schon in dem Beispiel. Sollte in 'run()' eine Speicheranforderung fehlschlagen, dann fang ich die Exception außerhalb auf, und auch wenn sonst etwas schiefgeht, dann kann ich auch alle anderen Exceptions auffangen und gib dann genau die gleiche Fehlermeldung wie du mit deinem Code aus.
Sie gibt nicht die gleiche Fehlermeldung aus, sie gibt 'No Memory' statt 'Failure' aus.
Dass run() nicht durchläuft ist nicht gleichbedeutend mit einem nicht existierendem Objekt. Das Objekt kann in der Lage sein, andere Aufgaben zu lösen. Ein zweiter Anlauf mit run(), zum Beispiel mit dem 2. Datensatz, kann erfolgreich laufen.
Du hast keine Information, ob run() ein Problem hat, oder ob das Objekt gar nicht existiert.
Bitte verkauf mir eine halbgare Lösung nicht als Vorteil.
Tut mir leid, wenn ich das so sage, aber Du dokterst hier schon seit einiger Zeit mit Exceptions rum, obwohl Dir noch ein Problembewußtsein fehlt, was es eigentlich bedeutet, wenn ein Fehler auftritt.
Deine Exceptionlösungen sind nicht equivalent, teilweise sind sie sogar falsch. Das hier ist nur ein Thread, eine Diskussion, hier werden keine Codes getestet, aber es nutzt nichts, zu debatieren, wenn wir nicht auf gleichem Level Fehlerbehandlung durchführen.
Heißt: Entweder Du bohrst Dein Beispiel entsprechend auf, oder Du packst noch die Fehlerbehandlung dazu, die bad_alloc in ... umwandelt, damit das ganze funktioniert.
Kerli hat geschrieben:Nein, ich will keine Strings weiterleiten, sondern statt einem Fehlercode einen Fehlerstring weitergeben. Oder findest du etwa die alte Variante mit verschiedensten Funktionen a la 'getLastError()' oder 'getErrorString(int error_code)' schön ist? Und wenn man dann noch verschiedene Bibliotheken hat, dann muss man noch für jede Bibliothek die richtige Funktion aufrufen.
GetLastError() hat den Nachteil (üblicherweise) keine History mitzuführen, dafür verbraucht GetLastError() keine Rechenzeit, um Informationen mitzuschleifen, die häufig nicht interessieren.
Schön ist die falsche Priorität hier, es gibt keine schöne Lösung, es gibt nur Lösungen die sinnvoll oder weniger sinnvoll sind.
Kerli hat geschrieben:Aber es gibt durchaus auch sinnvolle Anwendungsfälle. zb: ...
Kerli hat geschrieben:Wie würdest du so etwas ohne Exceptions vernünftig machen? Du müsstest ja nach jedem new händisch überprüfen ob du den gewünschten Speicher auch bekommen hast.
Vollkommen richtig und ich würde in jedem Fall wissen, wie der Status meines Algorithmus' ist und die Bedeutung abschätzen können.
Speicheranfordern kann innerhalb eines Algorithmus' häufiger vorkommen. Dass die Sachen hintereinander vorkommen ist ungewöhnlich, was das Beispiel zwar nett, aber vergleichsweise uninteressant macht.
Im Prinzip würde ich die Diskussion hier gerne einstampfen, denn ich führe sie nicht zum ersten Mal und die Texte werden auch nicht kürzer.
Und sie nimmt den Verlauf, den sie meistens nimmt, grade mit jüngeren Entwicklern, die glauben, dass die Mittel, die sie an der Hand haben gut sind und dass Entwickler den Verlauf einer Entwicklung kontrollieren können. Die einzige relativ zuverlässige Kontrolle, die existiert, ist die semantische Prüfung durch den Compiler und jedes Feature, dass die Kontrolle des Compilers zu leicht aushebelt, arbeitet gegen mich.
Als zweite Instanz folgen Unit-Tests, die sind nicht zuverlässig, weil unvollständig, aber sie liefern zumindest Indizien.
Der Mensch ist - auch als professioneller Entwickler - ist nur Richtungsgeber, aber nicht in der Lage, korrekte Fehlerbehandlung zu garantieren. Mehr als versuchen kann er es nicht. Ein fortgeschrittener Entwickler kann hervorragend programmeiren können, wunderbar debuggen und kennt alle Tricks, Fehler zu vermeiden. Ein professioneller Entwickler sollte sich aber genauso intensiv um die Fehler kümmern, die er nicht greifen kann und das bedeutet in erster Linie dafür zu sorgen, dass der Mensch möglichst nicht in die Lage versetzt wird, zuverlässige Kontrollen auszuhebeln und dabei doch alle Möglichkeiten behält, Anweisungen zu geben. Ein fortgeschrittener Programmierer muss nur eine Stunde Kopfschmerzen haben und passt an der entscheidenen Stelle nicht auf und dann bleiben nur noch semantische Analyse und Unit-Tests, um das abzufangen. Und Unit-Tests gibt es meistens nicht. Man wird für Features bezahlt, nicht für Unit-Tests.
Darum setzte ich die Casts (im anderen Thread) in Funktionen, weil hier der Kontrollverlust wenigstens einmalig an klar definierten Stellen stattfindet. Darum bin ich gegen Exceptions, weil sie dazu verleiten, Typen wiederzuverwerten und damit die semantische Analyse untergraben und so absichtlich, wie versehentlich, Probleme zusammenfassen, die wie Kerli mit seinem Beispiel zeigt, nicht zusammengehören.
Das zusätzliche if ist günstiger als Exceptions. Man kann es weglassen, aber ein fortgeschrittener Entwickler, der so arbeitet, sieht, wenn Resourcen angefordert werden ohne dass ein Wächter da steht, oder der Algrithmus in die Breite geht. Für einen erfahrenen Entwickler steht genau an der Stelle ein 'FALSCH' im Code, während bei Exceptions die Fehlerbehandlung irgendwo anders passiert - oder auch nicht, das sieht man eben nicht.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.
Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.