Fehlerbehandlung in einer Engine



  • knivil schrieb:

    asc schrieb:

    Dann hat jemand try/catch und RAII nicht verstanden.

    Ich weise einfach nochmal darauf hin, dass ich RAII auch mit if machen kann und ich es deswegen nicht als Argument zaehle. Sind gute Gruende fuer Exceptions, ohne gleich RAII ins Bot zu holen, schwer zu finden?

    1. Rede ich hier von dem, was normalerweise unter RAII verstanden wird. Und nein, das lässt sich nicht sinnvoll mit if-Verzweigungen nachbauen (Zumindest wenn in dem Code außer Rückgabewerten noch exceptions möglich sind).

    2. Ich esse mein Essen in der Regel mit Besteck, auch wenn man die Hände verwenden, und Besteck auch für anderes als Essen nutzen kann. Am meisten Sinn ergeben sie vor allem in Kombination. Ähnlich ist es mit RAII und dem Thema Exceptions - Gerade in C++ ist der Sinn des Einen, vor allem (Aber nicht Ausschließlich) in der Kombination mit dem Anderen sinnvoll.

    knivil schrieb:

    asc schrieb:

    Es ist ein Extrembeispiel, dennoch kann es prozedural niemals auf eine Weise vereinfacht werden, die Exceptions.

    Wage Behauptung, leider habe ich keine Lust dir das Gegenteil zu programmieren.

    Schade, da ich mal sehen will wie du das hinbekommen willst. Mit RAII & Exceptions wäre das Beispiel (vorausgesetzt man schreibt zudem auch die RAII-Objekte) ein 20 Zeiler, mit if-Schachtelungen wirst du das nicht hinbekommen. Und selbst wenn wir den Code der RAII-Objekte mitrechnen, wirst du über ein Gesamtprojekt eher ganz massiv Code einsparen (Da man diese Objekte dann auch in mehr als einen Aufruf verwendet).

    knivil schrieb:

    asc schrieb:

    aber sie ermöglichen einen sauberen Programmierstil

    Und if verhindert keinen sauberen Programmierstil (RAII siehe Anfang). So, what's the point?

    (Zumal das Beispiel kaum was mit sauberer Programmierung und C++ zu tun hat. Suchen wir uns einen schwachen Gegener (wie dieses Programm) und bauen unsere Argumentation darauf auf. Ein elegantes Beispiel, was durch Exceptions noch eleganter wird, ist wesentlich ueberzeugender.)

    Beispiel gefällig?

    Nehmen wir mal an, eine Klasse alloziert mehrere Daten, die möglichst noch aufeinander aufbauen. Der Konstruktor muss dabei eine konsistente Klasse hinterlassen.

    Mit RAII und Exceptions, sähe dies vielleicht wie folgt aus:

    // Seien a-c Smartpointer)
    Bar::Bar()
    :  a(new A()),
       b(new B(a),
       c(new C(a, b))
    {
    }
    

    Anschließend ist entweder die Klasse im konsistenten Zustand, oder die Konstruktion mittels Exception abgebrochen, und alle bis dahin allozierten Daten freigegeben. Viel Spaß mit deiner if-Schachtel-Orgie...

    cu André



  • knivil schrieb:

    Exceptions machen durchaus Sinn ohne RAII.

    Ne, nicht wirklich. Schonmal Java Code gesehen?

    File f=null;
    try {
      f=new File();
    }
    catch(Exception e) {
    }
    finally {
      if(f!=null) f.close();
    }
    

    Exceptions ohne RAII sind nicht wirklich besser als Errorcodes.

    Denn sehen wir uns den obigen Code mit RAII an:

    using(File f=new File()) {
    }
    

    welcher code ist wohl besser?



  • RAII verstanden wird. Und nein, das lässt sich nicht sinnvoll mit if-Verzweigungen nachbauen

    Ich will RAII nicht mit if nachbauen. Du scheint mich missverstanden zu haben.

    Mit RAII & Exceptions

    Wuerdest du RAII ohne Exceptions zaehlen lassen?

    Bar::Bar()
    :  a(new A()),
       b(new B(a),
       c(new C(a, b))
    {
    }
    

    Ich mag kein new in Initialisierungslisten (soll hier die nothrow-Variante sein), ich haette es so aehnlich gemacht (ungetestet):

    // seien a - c normale pointer :-)
    Bar::Bar(): a(0), b(0),c(0), valid_(false)
    {
        bool b = true;
        b = b && (a = new A());
        b = b && (b = ...
        ...
        valid_ = b;
    }
    
    Bar::~Bar()
    {
        delete a;
        delete b;
        delete c;
    }
    
    bool Bar::valid() { return valid_; }
    

    Klar kannst du jetzt sagen: Was wenn Klasse C ebenfalls Speicher anfordert und im ungueltigen Zustand ist? Bei solch einer Programmstruktur machen Exceptions schon Sinn, aber es bleibt ein kuenstliches Beispiel. Ich wuerde das uebergeordnete Problem dann wahrscheinlich auf andere Weise loesen.

    @Shade@work: Ja, der Javacode ist wirklich haesslich.

    Ein anderer Gesichtspunkt ist auch, wenn ich z.B. in C++ shared-object (DLLs) fuer z.B. Matlab schreibe. Ich moechte nicht auf die Datenstrukturen wie std::map, Iteratoren oder vorgefertigte Algorithmen verzichten. So wird die Bibliothek in C++ geschrieben und mittels C-Funktionen gewrapt. Da sind Exceptions einfach fehl am Platz.

    PS: Man kann auch auf Exceptions verzichten ohne gleich in if-Orgien zu enden. Mir ist das bis jetzt geglueckt. So, what's the point?



  • knivil schrieb:

    Ich mag kein new in Initialisierungslisten, ich haette es so aehnlich gemacht (ungetestet):

    Und was wenn die initialisierung nicht mit new ist, sondern es eben resourcen wie zB dateien sind, die über den ctor initialisiert werden? Willst du dann default ctor aufrufe haben?

    Ich habe deinen code mal korrigiert.

    // seien a - c normale pointer :-)
    Bar::Bar(): a(0), b(0),c(0), valid_(false)
    {
        bool f = true;
        f = f && (a = new A());
        f = f && (b = new B());
        f = f && (c = new C());
        valid_ = f;
    }
    
    bool Bar::valid() { return valid_; }
    

    Naja, jetzt das ganze mit Exceptions:

    Bar::Bar(): a(new A()), b(new B()),c(new C())
    {
    }
    

    mhm.

    Welche Code ist besser?

    Bedenke: beide Varianten arbeiten mit RAII.

    PS: Man kann auch auf Exceptions verzichten ohne gleich in if-Orgien zu enden. Mir ist das bis jetzt geglueckt. So, what's the point?

    Der Punkt Horizont Erweiterung. Du baust bei trivialen Klassen deutlich mehr Code als ich. Und mein Code ist dennoch Fehlerresistenter.

    Das hat nichts damit zu tun ob ich jetzt besser programmieren kann oder nicht, sondern einfach mit moderner technik. c++ ist was fehlerbehandlung betrifft den meisten sprachen um welten voraus und es wäre schön wenn alle c++ programmierer das einsehen würden und modernen code schreiben würden.

    Bring mir deshalb bitte ein Beispiel wo du denkst dass dein error-flag ansatz sehr schön zu tragen kommt. und ich schreib dir den code moderner und schöner neu.

    was hältst du davon?
    das beispiel hier mit 3 resourcen die es zu initialisieren gibt ist zB ein guter anfang: man hat immer irgendwelche resourcen die man initialisieren muss.

    die moderne variante hat 0 zeilen fehlerbehandlung, dein code hat 6 zeilen.

    lässt sich beliebig skalieren das ganze 😉

    ich muss aber zugeben hier etwas missionarisch zu sein, da ich immer wieder solchen code wie deinen sehe und dann sehe wie die leute zu java oder dergleichen wechseln weil dort fehlerbehandlung bequemer ist. und das finde ich schade - da resourcen verwaltung in c++ eine sehr sehr schöne lösung bietet wie sie in anderen sprachen nur sehr selten angetroffen wird.

    nur leider kennen die wenigsten leute diese techniken 😞



  • audacia schrieb:

    Nachtrag:
    Gerade bin ich wieder über ein Stück Code gestolpert, das hervorragend demonstriert, warum Exceptions besser sind.
    http://blogs.msdn.com/oldnewthing/archive/2004/07/20/188696.aspx

    So was kann man aber auch mit labels/gotos lösen - soll zwar kein Argument ggn Exceptions sein, aber hier hatte ma wer im forum gezeigt, dass sowas doch nen ganz gutes anwendungsgebiet für label/goto ist ^^ (die einrückungsebenen entfallen halt)

    bb



  • Mal eine Frage zu Exceptions. Wenn ich eine verzweigtes Programm habe und dort zB. eine Klasse TextureManager vorkommt. (Ich nehme mal das bereits genannte Beispiel; das passt gut) Die Klasse hat eine Methode LoadAll oder Ähnliches. So, diese Funktion kann jetzt aus was für Gründen auch immer die fünfte Textur von meinetwegen zehn nicht laden und wirft eine Exception.

    Insgesamt haben wir aber nur eine zentrales Try-Catch-Konstrukt (das war ja eine positive Seite von Exceptions), dann springt das Programm ja dank der Exception aus dem kompletten Programmfluss und landet im Exception-Handler. Jetzt können wir ja schön den Fehler begutachten, da ja alle Informationen in der Exception-Klasse gespeichert sind, aber zurück in den Programmfluss kommen wir ja nicht. Dank RAII wird dann ja der Speicher aller bereits geladenen Texturen freigegeben werden und man müsste die Routine - oder schlimmstenfalls das halbe Programm - nochmal aufrufen und alle Texturen neuladen. Viel sinnvoller wäre es doch, wenn man direkt an dezentraler Stelle darauf reagieren könnte und somit ohne Exceptions.

    Oder sollte man diesen Fall gar nicht als Exception behandeln, sondern eher mit Rückgabewert oder Ähnliches? Immerhin soll der Benutzer der Engine (oder halt nur der Klasse) selbst entscheiden können, wie er darauf reagiert, ob er dann eine Default-Textur laden lassen will oder doch lieber das Programm usw. usf.

    Irgendwo habe ich hier noch einen Denkfehler drinne, denn so passt ist das ja nicht allzu nützlich, da man dadurch ja viel zu unflexibel wäre.



  • knivil schrieb:

    Wuerdest du RAII ohne Exceptions zaehlen lassen?

    Ich finde das RAII ohne Exceptions bei weiten nicht mehr den Nutzen hat, wie mit Exceptions.

    knivil schrieb:

    Ich mag kein new in Initialisierungslisten...

    Das ist deine Entscheidung, ich verwende wiederum gerade dort new um den Nötigen Code auf ein Minimum zu reduzieren. Weniger Code stellt auch weniger Fehlerquellen zur Verfügung, zudem hat meine Behandlung gegenüber deiner Variante IMHO ausschließlich Vorteile (bzw. nenne mir mal Nachteile).

    Vorteile:
    1. Keine unnötigen nachträglichen Zuweisungen.
    2. Im Fehlerfall wird die Konstruktion automatisch rückgängig gemacht.
    3. Gleichzeitige Reduzierung des benötigten Code und weniger Fehlerquellen.
    4. Das Objekt ist gültig, und Bedarf dazu keiner weiteren Behandlung von Außen wie Validierungsprüfungen. Einzig die Fehlerbehandlung muss einmal geschrieben werden - und zwar genau an der Stelle die darauf reagieren kann.
    5. Das ist aber nur die Kür: Ich spare mir zudem die Implementation des Destruktors.

    knivil schrieb:

    Klar kannst du jetzt sagen: Was wenn Klasse C ebenfalls Speicher anfordert und im ungueltigen Zustand ist? Bei solch einer Programmstruktur machen Exceptions schon Sinn, aber es bleibt ein kuenstliches Beispiel. Ich wuerde das uebergeordnete Problem dann wahrscheinlich auf andere Weise loesen.

    Mal ganz davon abgesehen das ein Code wie von dir gezeigt, mir in Projekten schon etliche Tage der Fehlersuche gekostet haben, und bei leibe kein künstliches Beispiel sind. Mein Ansatz hat aber noch einen weitere Vorteil:

    6. Ich muss den Code nicht umschreiben wenn ein Objekt das ich alloziere wiederum Speicher alloziert. Sondern habe eine saubere Form der "Transaktion" (Im Fehlerfall wird alles automatisch aufgeräumt, und eine Sonderbehandlung ist nur an einer Stelle nötig [Ein try/catch Block - sofern man den Fehler behandeln kann]).

    knivil schrieb:

    Ein anderer Gesichtspunkt ist auch, wenn ich z.B. in C++ shared-object (DLLs) fuer z.B. Matlab schreibe. Ich moechte nicht auf die Datenstrukturen wie std::map, Iteratoren oder vorgefertigte Algorithmen verzichten. So wird die Bibliothek in C++ geschrieben und mittels C-Funktionen gewrapt. Da sind Exceptions einfach fehl am Platz.

    Das ist die einzige Stelle, an der ich Exceptions "wrappe". Sprich, ich wandel in der Schnittstelle Exceptions durch Fehlercodes. Konkret verwende ich Fehlercodes nur und ausschließlich an C-Schnittstellen.

    knivil schrieb:

    PS: Man kann auch auf Exceptions verzichten ohne gleich in if-Orgien zu enden. Mir ist das bis jetzt geglueckt. So, what's the point?

    Also ich sehe in deinen Code schon einige ifs, die ich mir gänzlich spare. Mein Konstruktor besteht in meinen Codestil aus 6 Zeilen, deiner würde darin mindestens 12 + Benötigten Destruktor + Benötigte Validierungslogik kosten.

    Ich sehe in deinem Weg keinerlei Vorteile gegenüber Meinen. Zeig mir doch mal bitte ein wirklich guten Grund gegen meine Variante, oder nenn mir bitte auch einen wirklichen Nachteil, der die Vorteile überwiegt.

    Zudem musst du eh Exceptionhandling einsetzen, zumindest wenn du ernsthaft die STL verwendest.

    cu André
    P.S: Ohne dir nahe zu treten, erinnert mich bislang die Argumentation an meinen letzten Programmierchef. Dieser war sauer weil ich bei den selbstgeschriebenen Smartpointern im Falle eines NULL-Zugriffes eine Exception geworfen habe (mit Fehlermeldung zu welchen Templatetyp etc. um die Fehlersuche zu erleichtern). Ich habe lieber einen kontrollierten Absturz mit einer Fehlermeldung (um den Fehler suchen zu können), als eine Access Violation mit unkontrollierten Absturz.



  • issen1 schrieb:

    ...So, diese Funktion kann jetzt aus was für Gründen auch immer die fünfte Textur von meinetwegen zehn nicht laden und wirft eine Exception....

    Insgesamt haben wir aber nur eine zentrales Try-Catch-Konstrukt (das war ja eine positive Seite von Exceptions)...

    Falsch. Wir haben ein try/catch Konstrukt, an der Stelle die den Fehler behandeln kann, nicht ein zentrales. Insgesamt wird es mehrere try/catch-Blöcke in einer großen Anwendung geben, nichts desto trotz bei sauberer Programmierung deutlich weniger Codezeilen als eine Einzelbehandlung von Fehlercodes.

    Und nun denk nochmal über deinen restlichen Text nach 😉

    cu André



  • Letztendlich verstehe ich eure Punkte (auch in Bezug auf die missionarische Verpflichtung). Geht mir bei anderen Sachen aehnlich (bin nur weniger emotional). Zu meiner Herangehensweise: Ich versuche fehlerfreien Code abzuliefern. Auch ist der 6 Zeilen Umweg um Exceptions herum nicht schoen (aber auch nicht haesslich) und in der Implementation verborgen.

    Ich sehe in deinem Weg keinerlei Vorteile gegenüber Meinen.

    So fuer sich genommen ist deine Loesung wahrscheinlich besser. Ein Vorteil faellt mir aber noch ein: Ich traue meine Anwendern (, die die Funktion benutzen) manchmal nicht wirklich zu, mit den Exceptions richtig umzugehen.

    Jetzt können wir ja schön den Fehler begutachten, da ja alle Informationen in der Exception-Klasse gespeichert sind

    Fuer Debugging gibt es andere Moeglichkeiten, ich wuerde Exceptions nicht dafuer missbrauchen. Auch was deine Smartpointer-Exceptions betrifft, finde ich sie fuer die Fehlersuche ungeeignet. Ausnahme: assert, da es im Release dann automatisch verschwindet. Aber deswegen sauer zu werden, so emotional bin ich da nicht.



  • unskilled schrieb:

    So was kann man aber auch mit labels/gotos lösen - soll zwar kein Argument ggn Exceptions sein, aber hier hatte ma wer im forum gezeigt, dass sowas doch nen ganz gutes anwendungsgebiet für label/goto ist ^^ (die einrückungsebenen entfallen halt)

    Siehe meine Artikel 😉
    Aber es ist immer noch wahnsinn im vergleich zu exceptions.

    issen1 schrieb:

    Viel sinnvoller wäre es doch, wenn man direkt an dezentraler Stelle darauf reagieren könnte und somit ohne Exceptions.

    Kannst du mit Exceptions ja. Exceptions bietet die Moeglichkeit an Zentral auf Fehler zu reagieren, aber sie beschneiden nicht die Option auch lokal zu reagieren wenn es sinn macht. Meistens macht es wenig Sinn lokal zu reagieren, deshalb sind error codes ja so schlecht, weil ich immer lokal reagieren muss.

    mit exceptions reagiere ich prinzipiell an wenigen zentralen stellen auf die fehler und in besonderen situationen eben lokal.

    knivil schrieb:

    Ich versuche fehlerfreien Code abzuliefern. Auch ist der 6 Zeilen Umweg um Exceptions herum nicht schoen (aber auch nicht haesslich) und in der Implementation verborgen.

    Aber unnoetig und welchen vorteil bringt er dir?

    du baust dir die moeglichkeiten fuer mehr fehler ein, da du ja in dem fehlerbehandlungscode fehler machen koenntest und ein code review mit einer niedrigeren wahrscheinlichkeit fehler findet. natuerlich nicht um viel. der unterschied ist hier minimal. aber rechne das ganze jetzt auf millionen von zeilen auf. immer ein bisschen schlechter - das macht am ende dann doch etwas aus.

    So fuer sich genommen ist deine Loesung wahrscheinlich besser. Ein Vorteil faellt mir aber noch ein: Ich traue meine Anwendern (, die die Funktion benutzen) manchmal nicht wirklich zu, mit den Exceptions richtig umzugehen.

    Aber mit error codes?

    Das verstehe ich nicht.
    kleines beispiel:

    file f("datei.txt");
    while(!f.eof()) {
      f.read();
    }
    

    mit deiner variante haben wir eine endlosschleife gebaut wenn "datei.txt" nicht geoeffnet werden kann. mit der exception variante haben wir vermutlich das programm mit der meldung "datei.txt konnte nicht geoffnet werden" abgeschossen.

    inwiefern ist es leichter wenn fehler aus versehen ignoriert werden koennen?

    Fuer Debugging gibt es andere Moeglichkeiten, ich wuerde Exceptions nicht dafuer missbrauchen.

    Exceptions liefern dir diese infos gratis. ohne aufwand.

    natuerlich debugt man nicht mit ihnen, aber es ist halt schon praktisch wenn du zB wie in java eine exception bekommst, auf sie doppelt klickst und zu der stelle springst an der sie geworfen wurde. unheimlich zeitsparend.

    natuerlich helfen hier asserts auch. selbes prinzip.

    aber was interessant ist, du kannst fehlermeldunden an den user viel schoener gestalten wenn du exceptions hast. denn du kennst zB den datei namen der nicht gefunden wurde. den kennst du mit error codes nicht... zumindest nicht ohne weiteres - bei exceptions ist es gratis dabei.



  • knivil schrieb:

    Zu meiner Herangehensweise: Ich versuche fehlerfreien Code abzuliefern.

    Das gleiche gilt für diejenigen die RAII und Exceptions vorziehen. Ich bin zudem der Meinung das kürzerer Code in der Regel leicher auf Fehler zu testen ist, als langer.

    knivil schrieb:

    Auch ist der 6 Zeilen Umweg um Exceptions herum nicht schoen (aber auch nicht haesslich) und in der Implementation verborgen.

    Welcher Umweg? Ich nutze hier explizit das Exceptionhandling aus.

    knivil schrieb:

    Ein Vorteil faellt mir aber noch ein: Ich traue meine Anwendern (, die die Funktion benutzen) manchmal nicht wirklich zu, mit den Exceptions richtig umzugehen.

    Das geht mir genau umgekehrt: Eine Exception die nicht abgefangen wird bemerkt man. Ein vergessener Rückgabewert kann wiederum in aller Stille sein destruktive Wirkung entfalten. Und dies ist auch nicht gekünstelt. Ich habe schon sehr viele Fälle erlebt in denen Rückgabewerte vergessen wurden.

    knivil schrieb:

    Auch was deine Smartpointer-Exceptions betrifft, finde ich sie fuer die Fehlersuche ungeeignet. Ausnahme: assert, da es im Release dann automatisch verschwindet

    Assert, schön und gut, aber der Anwender wird so oder so eine Fehlermeldung in meinen Fall erhalten: Entweder ich ignoriere es (an der Stelle geht keine Fehlerbehandlung mehr => Access Violation) oder ich zeige einen Fehler an, in der Hoffnung das unser Support von der Meldung erfährt. Was ist besser?

    Ich bin der Meinung das echte Fehler auch eine Fehlermeldung erzeugen dürfen.

    cu André



  • Du kannst in die while-Schleife ein is_open() einbauen.

    Okay Okay Okay, ich gebe auf. Eure Mission war erfolgreich. Ich bin nicht bekehrt, aber ziehe Exception in Zukunft bei meinen C++ Projekten ernsthaft in Erwaegung. Zufrieden? 🙂



  • knivil schrieb:

    Du kannst in die while-Schleife ein is_open() einbauen.

    Okay Okay Okay, ich gebe auf. Eure Mission war erfolgreich. Ich bin nicht bekehrt, aber ziehe Exception in Zukunft bei meinen C++ Projekten ernsthaft in Erwaegung. Zufrieden? 🙂

    Bei deinen Klassen muss ich dann aber nach einem new() immer valid() aufrufen und wenn valid() false zurückliefert weiß ich erst nicht was jetzt schief gelaufen ist.



  • knivil schrieb:

    Eure Mission war erfolgreich.

    Welche Mission? Ich sehe bislang nur Argumente 😉

    knivil schrieb:

    Ich bin nicht bekehrt, aber ziehe Exception in Zukunft bei meinen C++ Projekten ernsthaft in Erwaegung. Zufrieden? 🙂

    Ich bin bereits zufrieden wenn jemand Exceptions und RAII nicht verteufelt, und zumindest in Zukunft abwägt.

    cu André



  • asc schrieb:

    knivil schrieb:

    Ich bin nicht bekehrt, aber ziehe Exception in Zukunft bei meinen C++ Projekten ernsthaft in Erwaegung. Zufrieden? 🙂

    Ich bin bereits zufrieden wenn jemand Exceptions und RAII nicht verteufelt, und zumindest in Zukunft abwägt.

    Mit solchen Leuten rede ich gar nicht erst. Rate dir das auch zu tun, schont die Nerven 🙂



  • Also ich hab mich auch überzeugen lassen und arbeite jetzt schon ein Stück damit. Und bin ehrlich gesagt zufrieden, vor allem da ich in die Exceptions wirklich alles stopfen kann, wo ich bei Errorcodes wirklich gescheitert war.


Anmelden zum Antworten