Meinungsfrage zu Exceptions



  • Nachdem letztens der Thread oben war über Fehlerbehandlung in Engines bei dem einige Interessante Aspekte angesprochen wurden arbeite ich zzt etwas damit herum und bin mir aber unschlüßig wie weit man die Exceptions aufsplitten sollte.

    Mein derzeitiger Lösungsansatz sieht vor das ich eine Grund Exceptionklasse habe von der alle anderen Exceptions erben. Und zwar auf eine Art und weise durch die man anhand der Exception schond irekt sieht wo sie ehr komt, dh das von dieser Basis-Exceptionklasse erstmal eine Exception Klasse pro Klasse die Exceptions werfen soll abgeleitet wird und diese dann die basis für die Eigentlichen Exceptions bietet, Beispiel, man hat nen Texturmanager, dann gibt es die Basis Exception von der wird eine Texturmanager Exception abgeleitet vond er dann wiederrum alle Exceptions die aus dem texturmanager kommen können abgeleitet werden.

    Kernproblem der Sache ist das dabei sehr viele verschiedene Exceptions vorkommen, meine Modelklasse verwendet 5 oder 6 Klassen die Exceptions werfen können und hat durch das durchleiten am ende das Problem das sie selbst auf mehr als ein dutzend Exceptions aus allen Ecken kommen kann. Mein Texturmanager wirft zB eine Exception pro Meldung die D3DXCreateTexture zurückliefert (das wären allein 5).

    Ein anderer Ansatzpunkt den ich mir angesehen hatte war einfahc nur eine Exception zu werfen und sie mit den daten zu füllen, also den Rückgabe wert quasi durchzuschleifen und somit die gesammtanzahl der Exceptions auf 1 Pro Klasse zu reduzieren, dort sehe ich aber das Problem das man dann einige ifs oder switch bräuchte um zu sehen was Sache ist.

    Daher mal meine Frage an euch wa sist eure Meinung wie sollte man das angehen, Möglichst viele evrschiedene Exceptions werfen um sicherzugehen das der Nutezr auch wirklich alle Möglichen zu werfenden Fehler abfangen muß oder eher zusammenfassen und riskieren das er zwar die Exception abfängt aber den Fehler der entstanden ist nicht löst weil er den Inhalt der Exception nicht komplett beachtet?



  • Xebov schrieb:

    ...

    Brauchen die unterschiedlichen Fehler denn auch wirklich alle eine unterschiedliche Behandlung? Sind überhaupt alle Fehler behandelbar?



  • Xebov schrieb:

    Kernproblem der Sache ist das dabei sehr viele verschiedene Exceptions vorkommen, meine Modelklasse verwendet 5 oder 6 Klassen die Exceptions werfen können und hat durch das durchleiten am ende das Problem das sie selbst auf mehr als ein dutzend Exceptions aus allen Ecken kommen kann.

    Du vererbst die Exception Klassen schon, oder?
    Dann kannst du sehr fein zwischen einzelnen Fehlern unterscheiden wenn du das willst aber musst es nicht weil du einfach eine eltern klasse fangen kannst.



  • Mach beides.

    Es gibt Fehlercodes.

    enum ErrorCode
    {
        ErrorCode_Bla,
        ErrorCode_Blu,
    };
    

    Dann hast du deine BaseException.

    class BaseException
    {
    public:
        BaseException( ErrorCode _ErrorCode ){ mErrorCode = _ErrorCode;};
        ErrorCode getErrorCode( void ) const { return mErrorCode;};
    
    private:
        ErrorCode mErrorCode;
    };
    

    Und dann noch spezielle Exceptions.

    template< ErrorCode _ErrorCode >
    class SpecialException : public BaseException
    {
    public:
        SpecialException( void ) : mErrorCode( _ErrorCode ){};
    };
    

    Jetzt kannst du immer die exakte Exception werfen und musst nicht immer eine neue Klasse schreiben. Dennoch hat der Benutzer die Wahl, ob er eine spezielle Exception fangen möchte, oder nur wissen möchte ob überhaupt eine fliegt.



  • Tachyon schrieb:

    Xebov schrieb:

    ...

    Brauchen die unterschiedlichen Fehler denn auch wirklich alle eine unterschiedliche Behandlung? Sind überhaupt alle Fehler behandelbar?

    Naja da bin ich mir halt unsicher. Ich nehme mal meinen TexturManager, der wirft OutOfMemory wenn er sich nciht erweietrn kann oder TexturLadend as zurück wirft, dann wirft er OutOfVideoMemory, InvalidCall,InvalidData und NotAvailable, und da ist es halt sone Sache, für OutOfMemory und OutOfVideoMemory köntne man getrennt voneinander Daten freigeben jenachdem wo man was braucht, InvalidCall wäre wohl nicht behandelbar, NotAvailable wäre mit nem Standartaufruf der garantiert Funktioniert und InvalidData wäre mit ner StandartTextur lösbar, also wären schon 4 der 5 Fehler irgendwie Lösbar und bräuchten auch verschiedene Behandlungen.

    Shade Of Mine schrieb:

    Du vererbst die Exception Klassen schon, oder?
    Dann kannst du sehr fein zwischen einzelnen Fehlern unterscheiden wenn du das willst aber musst es nicht weil du einfach eine eltern klasse fangen kannst.

    Ja die werden von oben nach unten evrerbt man kann zzt alle Engine Fehler fangen, das wäre die basisklasse, dann kann man alle Fehler die eine Klasse werfen kann fangen, und spezielel Detailfehler.

    Fusel.Factor schrieb:

    Mach beides.

    Daran hab ich auch schon gedacht nur hab ich dabei Angst das wichtige details unterschlagen werden.



  • [quote="Xebov"]

    Tachyon schrieb:

    ...

    Out Of Memory ist so eine Sache. Vielleicht ist Deine Applikation gar nicht verantwortlich dafür, dass dem Rechner der Speicher ausgeht. Vielleicht hast irgendeine andere Applikation ein Leck. Das kannst Du weder wissen noch abfangen. Ähnliches gilt für den Videospeicher.
    Invalid Call könnte man durch ein ordentliches Interface abfangen, dass sowas gar nicht zulässt.
    Aber wie Shade schon sagt, wenn Deine Exceptions in einer Vererbungshirarchie sind, kannst Du es auch sehr fein einteilen, und musst trotzdem nur Deine BaseException fangen.



  • Xebov schrieb:

    also wären schon 4 der 5 Fehler irgendwie Lösbar und bräuchten auch verschiedene Behandlungen.

    Und wo genau ist dann das Problem?

    Du hast 5 Fehlerfälle von X die du behandeln kannst. Ergo brauchst du 5 mal Code den passenden Code.

    Zeig mal etwas Code, dann wird uU klarer was du genau meinst.



  • Shade Of Mine schrieb:

    Xebov schrieb:

    also wären schon 4 der 5 Fehler irgendwie Lösbar und bräuchten auch verschiedene Behandlungen.

    Und wo genau ist dann das Problem?

    Du hast 5 Fehlerfälle von X die du behandeln kannst. Ergo brauchst du 5 mal Code den passenden Code.

    Zeig mal etwas Code, dann wird uU klarer was du genau meinst.

    Ja das man jeweils passenden Code braucht ist klar, mir ging es mehr um das versenden von Daten, und ob ich evtl einfach zuviele Exceptions werfe, allerdings den Punkt einfach nur die Basisklasse abzufangen hab ich nicht gedacht.Das genaue Problem ist das ich nicht zuviele Exceptions werfen will aber genug das kein Fehler irgendwo verloren geht, und deswegen die Frage nach Meinung was sich mehr eignet zusammenfassen der Feller zu einer Exception mit zusatzinfos oder eben für jeden Fehler ne eigene Exception.

    Aber hier mal etwas Code

    //Basis
    struct e_bfError
    {};
    
    //bfTexturManager Basis
    struct e_bfTextureManager_Error:public e_bfError
    {};
    //bfTextureManager Exceptions
    //OutOfMemory
    struct e_bfTextureManager_OutOfMemory:public e_bfTextureManager_Error
    {};
    //OutOfVideoMemory
    struct e_bfTextureManager_OutOfVideoMemory:public e_bfTextureManager_Error
    {};
    //Datei nicht vorhanden/Dateiinhalt nicht gültig
    struct e_bfTextureManager_InvalidData:public e_bfTextureManager_Error
    {};
    //Funktionsaufruf nicht gültig
    struct e_bfTextureManager_InvalidCall:public e_bfTextureManager_Error
    {};
    //Aufgerufene Technik nicht verfügbar
    struct e_bfTextureManager_NotAvailable:public e_bfTextureManager_Error
    {};
    

    Das is ne Anordnung wie ich sie gewählt habe, bfTexturManager ist die Exception Klasse für meinen texturManager und erbt selbst wie alle EngineTeile von e_bfError, ihr untergeordnet sind alle spezifischen Fehler die der Texturmanager auslösen kann. Ich war mir nur eineins ob ich nicht zuviel werfe bzw zuviel evrschiedenes werfen kann, hatte allerdings nicht daran gedacht das man auch einfach die Basisklasse fangen kann wenn es einen nicht so genau interessiert.



  • Sry verklickt.



  • Hey, welcome in exception hell. Sehe ich das richtig, dass du mehr Klassen fuer Exception hast, als "normale" Klassen? Btw. Exceptions sollten nicht als Debuggingersatz herhalten. Du kannst auch einfach eine Exceptionklasse fuer fatal errors nutzen und in what() "reinschreiben", was schiefgegangen ist. Recovering von out_of_memory kannste ja eh nicht.



  • knivil schrieb:

    Hey, welcome in exception hell. Sehe ich das richtig, dass du mehr Klassen fuer Exception hast, als "normale" Klassen?

    Ähm ja, zzt kommen 12 Klassen die bereits schon Exceptions haben auf 24 Excepions (Basisklassen inklusive). Dabei soltle ich aber auch erwähnen das ich keine Exception wiederverwende, dh jede Klasse die inetrn new benutzt oder eine DX Komponente die OutOfMemory werfen kann ihre eigene OutOfMemory Exception hat, dh die kommen in dem Exception baum mehrfach vor.

    knivil schrieb:

    Btw. Exceptions sollten nicht als Debuggingersatz herhalten. Du kannst auch einfach eine Exceptionklasse fuer fatal errors nutzen und in what() "reinschreiben", was schiefgegangen ist. Recovering von out_of_memory kannste ja eh nicht.

    Ja ich benutze sie auch nichtd afür ich teile fehelr mit die durch Debuggen nicht lösbar sind, also zB nicht gefundene Daten oder Dateien, fehler beim holen von Speicher, usw. um what() zu haben muß man dafür seine Exceptions nicht von der Exceptionklasse aus der std ableiten?



  • Dein Fehler ist, dass du die Fehler nicht generalisierst.

    NotAvailable ist kein Fehler des Texturmanagers sondern generell eine nicht vorhandene Resource. OutOfMemory ist ebenfalls ein genereller Fehler und nicht Teil einer Klasse.

    Du hast also folgende Struktur:

    Error
    - MemoryError
    - - OutOfRam
    - - OutOfVRam
    - InvalidOperationError
    - - InvalidCall
    - - InvalidData
    - - InvalidState

    Und mit diesen Exceptions arbeitest du im ganzen Programm. Es ist egal wer den InvalidCall verursacht hat (ob der Texturmanager oder der ObjektManager oder sonstwer) - das ist nur dann direkt beim behandeln des fehlers interessant (wenn überhaupt).



  • Der Ansatz, dass verschiedene Klassen auch verschiedene Exceptions haben ist überflüssig. Der Grund ist folgender:
    Im Idealfall kannst du eine Exception nahe der Stelle fangen (und behandeln) wo sie auftritt. Das bedeutet aber, dass du weißt, ob sie jetzt aus einer Methode von Foo oder aus einer Methode von Bar kommt, weil der try-Block vermutlich nur eine solche Methode umschließt. In dem Fall machts also schonmal keinen Sinn, zwischen einer Bar_Bad_Dingsbums_Exception und einer Foo_Bad_Dingsbums_Exception zu unterscheiden.
    Im weniger günstigen Fall kannst du eine Exception nicht direkt behandeln sondern lässt sie durchrauschen (oder wandelst sie um). Weiter entfernt von dem eigentlichen Problem kannst du aber nicht das eigentliche Problem selbst behandeln (eben deshalb behandelst du sie ja erst weiter weg von ihrem Ursprung). Das bedeutet, du musst "weit im Gesunden abschneiden" - also den ganzen Foo-Bar-Komplex in die Tonne treten und was anderes versuchen. In dem Fall ist es also auch egal ob die Exception aus Foo oder aus Bar kommt, weils keinen Unterschied macht warum du alles wegwerfen musst.



  • Ja da habt ihr natürlich Recht vond er Seite aus hab ich das nie gesehen, und für den Herkunftsort würde ja zur Not auch ein Flag reichen das die Klasse angibt wo der Fehler herstammt. Die Variante von Shade gefällt mir auch bedeutend besser als meine, sie ist eindeutig sehr viel übersichtlicher als meine, vor allem wenn ich bedenke wieviele Exceptions mir noch fehlen würde umd as Konzept zu einem Ende zu bringen.



  • Ich nutze den Thread mal noch für ne Frage in die Runde.

    Ich hab mir gestern mal die Implementierung von Exception und what() angeschaut. Dabei ist mir aufgefallen das für die Nachricht die versendet wird extra Speicher angelegt wird, zwar mit malloc aber das is ja egal, daher mal meine Frage, angenommen new schlägt fehl und ich kann OutOfMemory werfen, gäbe es dann eine Garantie das ich Speicherplatz in der Exception für die Nachricht bekomme? also wird von vornherein ein Gewisser Speicher für Exceptions Reserviert um zu Garantieren das da nichts schiefgeht? Wenn nicht könnte ja Theoretisch der Konstruktor der Exception wenn man New Benutzt auch ne Exception werfen.



  • Xebov schrieb:

    Dabei ist mir aufgefallen das für die Nachricht die versendet wird extra Speicher angelegt wird, zwar mit malloc aber das is ja egal, daher mal meine Frage, angenommen new schlägt fehl und ich kann OutOfMemory werfen, gäbe es dann eine Garantie das ich Speicherplatz in der Exception für die Nachricht bekomme?

    Wenn auf einem modernen OS eine out of memory exception fliegt, dann bist du eh schon tot. Denn OOM geht ein system nur, wenn wirklich alles komplett voll ist. meistens ist es sogar so, dass new garnicht bad_alloc werfen kann, da das system meistens mit einem lazy commit arbeitet und so prinzipiell immer behauptet speicher zu haben.

    bei out of memory also am besten die anwendung beenden. (wenn das denn überhaupt machbar ist). ich fange bad_alloc zB explizit nicht ab und sterbe lieber als mich mit dem fall auseinander zu setzen 😉



  • Shade Of Mine schrieb:

    Wenn auf einem modernen OS eine out of memory exception fliegt, dann bist du eh schon tot. Denn OOM geht ein system nur, wenn wirklich alles komplett voll ist. meistens ist es sogar so, dass new garnicht bad_alloc werfen kann, da das system meistens mit einem lazy commit arbeitet und so prinzipiell immer behauptet speicher zu haben.

    bei out of memory also am besten die anwendung beenden. (wenn das denn überhaupt machbar ist). ich fange bad_alloc zB explizit nicht ab und sterbe lieber als mich mit dem fall auseinander zu setzen 😉

    Das heist also es wäre bad_alloc aber das bad_alloc bleibt irgendwo stecken. Gut zu wissen. Ich fange sie zzt ab und wandel sie um um einige Zeiger aufzuräumen die sonst als Speicherleck verschwinden würden. Aber danke für die Antwort dann kann ich ja beruhigt weiter dran rum basteln.


  • Administrator

    Xebov schrieb:

    Ich fange sie zzt ab und wandel sie um um einige Zeiger aufzuräumen die sonst als Speicherleck verschwinden würden. Aber danke für die Antwort dann kann ich ja beruhigt weiter dran rum basteln.

    !! RAII !!
    😃

    Du musst die Dinger nicht abfangen um aufzuräumen, wenn du dein Programm richtig aufgebaut hast, dann reichen die Destruktoren, welche automatisch aufgerufen werden 😉

    Übrigens, ein bad_alloc heisst nicht immer, dass der Speicher unbedingt voll ist. Es kann auch sein, dass zum Beispiel nicht die Menge Speicher, welche angefordert wurde, an einem Stück zu finden war. Kleinere Stücke können dann womöglich angelegt werden und wenn man ein paar Ebenen raufgeht, dann ist auch wieder Speicher vorhanden, meistens.

    Allerdings fange ich "Out Of Memory" und co eigentlich auch so gut wie nie ab. Hat meistens keinen Sinn.

    Grüssli



  • Dravere schrieb:

    !! RAII !!
    😃

    Du musst die Dinger nicht abfangen um aufzuräumen, wenn du dein Programm richtig aufgebaut hast, dann reichen die Destruktoren, welche automatisch aufgerufen werden 😉

    Ja ich verlasse mich aucb auf RAII abe rnicht immer, wenn ich zB eine temporäre Variable brauche erstelel ich die eben mit new und die muß ja dann auch gelöscht werden. Den ganzen Rest überlasse ich zum großteils chon RAII.

    Dravere schrieb:

    Übrigens, ein bad_alloc heisst nicht immer, dass der Speicher unbedingt voll ist. Es kann auch sein, dass zum Beispiel nicht die Menge Speicher, welche angefordert wurde, an einem Stück zu finden war. Kleinere Stücke können dann womöglich angelegt werden und wenn man ein paar Ebenen raufgeht, dann ist auch wieder Speicher vorhanden, meistens.

    Allerdings fange ich "Out Of Memory" und co eigentlich auch so gut wie nie ab. Hat meistens keinen Sinn.

    Naja ob der Speicher voll ist oder nur das Stück in der Größe nicht vorhanden ist ist ansich ja eh egal, das Objekt kann so und so auch nicht angelegt werden.

    Ich mache damit im Grunde auch nichts weiter ich nutze sie halt zum Aufräumen oder um zu verhindern das Daten ungültig werden. Also um in Klassen zum Beispiel Änderungen rückgängig zu machen die vor der Exception gemacht wurden, das gilt aber bei mir für alle Exceptions.

    Danke auf jedenfall für die Ausführliche Auskunft.


  • Administrator

    Xebov schrieb:

    Ja ich verlasse mich aucb auf RAII abe rnicht immer, wenn ich zB eine temporäre Variable brauche erstelel ich die eben mit new und die muß ja dann auch gelöscht werden. Den ganzen Rest überlasse ich zum großteils chon RAII.

    Für sowas gibt es Smart Pointer, bzw. der Scoped Pointer oder aktuell in der Standardbibliothek der auto_ptr:
    http://www.cplusplus.com/reference/std/memory/auto_ptr/

    void foo()
    {
      // Objekt temporär auf dem Heap anlegen:
      std::auto_ptr<BigObject> bigObjectPtr(new BigObject());
    
      bigObjectPtr->do_something();
    
      bar(bigObjectPtr);
    
      // usw.
    }
    
    // Speicher wird von std::auto_ptr automatisch freigegeben.
    

    Für die Smart Pointer empfiehlt es sich, mal bei Boost vorbeizuschauen:
    http://www.boost.org/doc/libs/1_38_0/libs/smart_ptr/smart_ptr.htm

    Grüssli


Log in to reply