Fehlerbehandlung in einer Engine



  • Xebov schrieb:

    Naja wer sagt dennd as man sie Ignoriert? ob ich einen Rückgabewert habe und denn gegenteste oder ob ich die Exception verarbeite läuft aufs gleiche hinaus,

    Nur ist der Weg ein anderer.
    Bei Error Codes musst du immer und überall ifs einbauen. IMMER.
    bei Exceptions hast du 0 Fehlerbehandlungscode im normalen Code drinnen.

    if(!InitDevice()) {
      return ID_FAILED;
    }
    if(!SetupScene()) {
      return SS_FAILED;
    }
    if(!RunLoop()) {
      return RL_FAILED;
    }
    

    versus

    InitDevice();
    SetupScene();
    RunLoop();
    

    Sollte erkennbar sein was schöner ist, oder?

    ich habe zB nur 3 Probleme die Auftreten können, NULL-Zeiger, fehlende Dateien oder das irgendeine der vielen DX Funktionen mal streikt, das gibt nen Eintrag ins Log mit genauen infos und dann halt ne Fehlerrückgabe und auf die muß man sofort Reagieren, wenn du 10 Models lädst und eins davon is nicht da dann musste ja direkt handeln

    Natürlich muss ich direkt handeln - aber nicht im LoadModel() sondern am komplett anderen Ende der Anwendung. Nämlich dort wo ich etwas tun kann. zB den User nach dem richtigen Verzeichnis fragen oder eine neuinstallation verlangen, etc.
    LoadModel selber weiss aber nix davon. Es sagt nur "kann nicht laden" und in einem komplett anderen Modul reagiere ich auf den Fehler. Und zwar jetzt und sofort - nur eben nicht dort wo der Fehler auftrat sondern dort wo ich ihn behandeln kann.

    Doch ich habe deine Artikel gelesen, den 1. und 2. komplett den Hinetr den Kulissen hab ich angefangen, allein die vielen Blöcke haben mich dann entgültig abgeschreckt weil ich mir dann dachte das kanns ja nicht sein, nen ganzen haufen Blöcke nur für die paar Zeilen Code das schreckt mich ab, warum ist ganz einfach, ich kann jede Menge Blöcke bauen oder eben einfach sagen ERR_FILE_NOT_FOUND, wovon letztere Variante genauso aussagekräftig ist wie die Exceptions, und sie ist direkt zu sehen, man muß sich da nicht durchkämpfen.

    Kapier ich nicht. Hast du folgenden Code gelesen?

    class Class { 
    private: 
      char* name; 
      int* array; 
    
    public: 
      Class(char const* name) 
      : name(0), array(0) 
      { 
        try { 
          this->name = new char[strlen(name)+1]; 
          array = new int[100]; 
        } 
        catch(std::bad_alloc& e) { 
          delete [] this->name; 
          delete [] array; 
          throw; 
        } 
        strcpy(this->name, name); 
        fill(array); 
      } 
    //... 
    };
    

    Alternativ auch mit if/else und 0 statt bad_alloc vorstellbar.

    Aber dank exceptions kann ich daraus folgendes machen:

    class Class { 
    private: 
      std::string name; 
      std::vector<int> array; 
    
    public: 
      Class(char const* name) 
      : name(name), array(100) 
      { 
        fill(array); 
      } 
    //... 
    };
    

    Und das ist es, was man verstehen muss.
    Diese kurze Variante von Class - sie ist nur mit exceptions möglich.

    Exceptions erlauben es uns massig Code zu sparen und dadurch den Code klarer gestalten zu können.

    Exceptions haben übrigens nichts mit der Schwere eines Fehlers zu tun. Bei manchen Fehlern ist ein sofortiges terminate() sogar das sinnvollste. Sie haben mit der Art eines Fehlers zu tun. Erkläre ich übrigens in den Artikel 😉

    Die schwere eines Fehlers wird übrigens auch nur vom Kontext bestimmt...



  • Scorcher24 schrieb:

    Ähm tschulligung, aber ich arbeite ja qausi immer mit ErroCodes (als Enum, ich rede hier nicht unbedingt von bool) und hab noch NIE ein goto gebraucht...
    rya.

    Das bedeutet du hast manchmal code verdopplung.



  • Scorcher24 schrieb:

    Wenn man sauber mit Errorcodes arbeiten will muss man zwangsweise mit goto arbeiten und man fängt an einen schlechten Exceptionmechanismus nachzubauen, wo ist also der Vorteil von Errorcodes, wenn man Exceptions bereits in der Sprache zur Verfügung hat? Und ich rede hier von Errorcodes, nicht von einem Bool, oder auch mal einem TriBool, für spezielle Funktionen.

    Ähm tschulligung, aber ich arbeite ja qausi immer mit ErroCodes (als Enum, ich rede hier nicht unbedingt von bool) und hab noch NIE ein goto gebraucht...
    rya.

    Wie löst Du dann z.B. folgendes Szenario elegant:

    • Es sollen u.U. unterschiedliche 10 Ressourcen (welche explizit wieder freigegenen werden müssen) allokiert werden.
    • Für einen validen Zustand müssen entweder alle 10 Ressourcen erfolgreich allokiert wurden sein, oder aber gar keine.
    • Beim Allokieren der 6. Ressource tritt ein Fehler auf, wodurch die bereits allokierten 5 Ressourcen wieder freigegeben werden müssen.
    • Der Fehler kann auch bei jeder anderen der 10 Allokationen auftreten.
    • Die Allokation der Ressourcen erfolgt mittels einer Funktion, welche im Fehlerfall einen entsprechenden Fehlercode zurück gibt.
    • Nach einem halben Jahr müssen drei weitere Ressourcen hinzugefügt werden, für welche die bereits genannten Punkte ebenfalls gelten.

    ?
    Mich interessiert vor allem die Freigabe der Ressourcen.

    Edit: kleiner Zusatz in Punkt 1



  • Tachyon schrieb:

    Mich interessiert vor allem die Freigabe der Ressourcen.

    RAII

    Nachtrag:

    aus einem

    template<typename ForwardIterator, typename T> 
    void uninitialized_fill(ForwardIterator first, ForwardIterator last, T const& obj) { 
        ScopeGuard guard = MakeGuard(makeDelayedRangeExecute(first, destroy)); 
        while(first!=last) { 
            new (&*first) T(obj); 
            ++first; 
        } 
        guard.Dismiss(); 
    }
    

    wird dann eben ein

    template<typename ForwardIterator, typename T> 
    bool uninitialized_fill(ForwardIterator first, ForwardIterator last, T const& obj) { 
        ScopeGuard guard = MakeGuard(makeDelayedRangeExecute(first, destroy)); 
        while(first!=last) { 
            if(!new (&*first) T(obj))
              return false;
            ++first; 
        } 
        guard.Dismiss(); 
        return true;
    }
    

    aber man sieht sehr schön dass wir plötzlich mehr code haben 😉



  • Shade Of Mine schrieb:

    Tachyon schrieb:

    Mich interessiert vor allem die Freigabe der Ressourcen.

    RAII

    Die Anwendung stelle ich mir aber ohne das Werfen von Exception nicht so einfach vor...



  • Zugegeben. In dem Fäll wäre der Code sicherlich zumindest unschön, aber ich denke mit Exceptions und einem try-catch wirds auch nicht viel besser..



  • alloc10resources
    {
      10 Lokale Objekte;
      b = true;
      b = b && alloc( Resource1 )
      b = b && alloc( Resource2 )
      ...
    
      if( b )
        copy resources or whatever smart_ptr
    }
    

    Z.B. so?



  • it0101@loggedoff schrieb:

    Zugegeben. In dem Fäll wäre der Code sicherlich zumindest unschön, aber ich denke mit Exceptions und einem try-catch wirds auch nicht viel besser..

    Bei einem throw räumt mir aber RAII alle vorher allokierte wieder auf. Man mus quasi nichts mehr machen. Im catch-Block sowieso nicht.



  • Tachyon schrieb:

    Shade Of Mine schrieb:

    Tachyon schrieb:

    Mich interessiert vor allem die Freigabe der Ressourcen.

    RAII

    Die Anwendung stelle ich mir aber ohne das Werfen von Exception nicht so einfach vor...

    Ja, es kostet mehr Code. Aber statt einem throw machst du halt return.

    Was dabei natürlich suckt ist die transaltion von low level auf high level error code. RAII ermöglicht es mit exception eben 0 fehlerbehandlungscode schreiben zu müssen. RAII mit error Codes ermöglicht es nur auf aufräumungsarbeiten verzichten zu können (die fehlerbehandlung selber muss man immer noch schreiben).

    Deshalb ist RAII mit exceptions so stark. ideal wäre natürlich wenn uncaught_exception überall funktionieren würde, dann wäre RAII noch um ein eck stärker...



  • knivil schrieb:

    alloc10resources
    {
      10 Lokale Objekte;
      b = true;
      b = b && alloc( Resource1 )
      b = b && alloc( Resource2 )
      ...
    
      if( b )
        copy resources or whatever smart_ptr
    }
    

    Z.B. so?

    Und woher weisst Du, bei welchem alloc das ganze daneben ging?



  • Shade Of Mine schrieb:

    ...Ja, es kostet mehr Code. Aber statt einem throw machst du halt return.

    Okay, das geht natürlich. Wobei man bei da bei Konstruktoren auf Fehlerinformationen verzichen muss. 🤡



  • Ist doch egal. Resourcendestruktor kuemmert sich um die korrekte Freigabe, RAII halt. Kannst auch wahlweise im Log nachsehen. alloc10res kann einfach b zurueckliefern.



  • knivil schrieb:

    Ist doch egal. Resourcendestruktor kuemmert sich um die korrekte Freigabe. Kannst auch wahlweise im Log nachsehen.

    Problem ist aber, woher weiss die ressource dass sie sich zerstören muss?

    Wenn ich ein LoadAllModels() mache - und beim 7. sterbe ich - woher wissen die anderen 6 dass sie ebenfalls sterben müssen?

    für sowas hat man denn etwas ähnliches wie mein DelayedRangeExecute (im prinzip also einen ScopeGuard) - aber ich habe das gefühl ich greife etwas zu weit voraus 😉

    Schau dir aber mal meinen Code an - und vergleiche die exception und die return code variante. Welche ist besser?

    Der Punkt ist:
    du hast mit exceptions weniger code zu schreiben und verwässerst dir den code nicht mit fehlerbehandlung. nebenbei sind dann noch so ein paar sachen nett ala schutz vor murphy - wenn du mal vergisst auf einen fehler zu reagieren, etc.



  • Die Ressource weiss, das sie geladen wurde oder nicht geldaen wurde. Ist 'nen Objekt und zustandsbehaftet. Sie weiss es einfach. Z.B. bool Texture.load(...) legt Speicher an kopiert Texturdaten rein. Naja, ist was fehlgeschlagen, dann ist der Speicherpointer halt 0. Lade ich jetzt meine 10 Texturen wird nur im Erfolgsfall kopiert, smart_ptr whatever ... . Falls nicht, wird der Destruktor der Resourcen aufgerufen.

    Btw. ich weiss schon was es mit RAII, Gards etc. auf sich hat. Aber es ging ja um Exceptions. Wenn bool Texture.load( ... ) 'ne Exception werfen wuerde, dann waere ich gezwungen mittels try-catch diese zu fangen. Hier gebe ich einfach nur false zurueck. Soll der Benutzer der Funktion sich darum kuemmern. (Nein es ist nicht der Anwender, sondern der Programmierer, der meine Funktion benutzt, meist ich selber).



  • knivil schrieb:

    Die Ressource weiss, das sie geladen wurde oder nicht geldaen wurde. Ist 'nen Objekt und zustandsbehaftet. Sie weiss es einfach. Z.B. bool Texture.load(...) legt Speicher an kopiert Texturdaten rein. Naja, ist was fehlgeschlagen, dann ist der Speicherpointer halt 0. Lade ich jetzt meine 10 Texturen wird nur im Erfolgsfall kopiert, smart_ptr whatever ... . Falls nicht, wird der Destruktor der Resourcen aufgerufen.

    Und wer ruft den Destruktor in welchem Fall auf, und wie stellst Du sicher, dass die Destruktoren im Erfolgsfall nicht beim Verlassen des Scopes aufgerufen würden, was Deine Smartpointer invalidieren würde?

    knivil schrieb:

    ...Soll der Benutzer der Funktion sich darum kuemmern. (Nein es ist nicht der Anwender, sondern der Programmierer, der meine Funktion benutzt, meist ich selber).

    Verstehe ich nicht. Wo ist der Unterschied zu Exceptions? Da kannst Du auch sagen, "soll sich der Benutzer drum kümmern". Du musst die Exception nicht fangen. Du musst Ihm nur sagen, dass eine bestimtme Exception geworfen werden kann, genauso wie Du Ihm sagen musst was es bedeutet, wenn Deine Funktion false zurückgibt.



  • Hui, da habe ich ja ganz schön was losgetreten 🙄

    Naja, jetzt kann ich mich auf jedenfall nicht beklagen, dass ich zu wenig Rückmeldungen hatte und das Thema nicht genug diskutiert wurde :).

    MfG
    Hundefutter

    P.S.: Diskutiert noch schön weiter, ist sehr interessant, aber bitte lasst alle am Leben 😉



  • Verstehe ich nicht. Wo ist der Unterschied zu Exceptions?

    q.e.d 😃



  • knivil schrieb:

    Die Ressource weiss, das sie geladen wurde oder nicht geldaen wurde. Ist 'nen Objekt und zustandsbehaftet. Sie weiss es einfach. Z.B. bool Texture.load(...) legt Speicher an kopiert Texturdaten rein. Naja, ist was fehlgeschlagen, dann ist der Speicherpointer halt 0. Lade ich jetzt meine 10 Texturen wird nur im Erfolgsfall kopiert, smart_ptr whatever ... . Falls nicht, wird der Destruktor der Resourcen aufgerufen.

    Aufpassen.
    Es geht darum WANN der Dtor aufgerufen werden soll. Und das muss er manchmal eben sofort (rollback) und nicht später.

    Btw. ich weiss schon was es mit RAII, Gards etc. auf sich hat. Aber es ging ja um Exceptions. Wenn bool Texture.load( ... ) 'ne Exception werfen wuerde, dann waere ich gezwungen mittels try-catch diese zu fangen. Hier gebe ich einfach nur false zurueck. Soll der Benutzer der Funktion sich darum kuemmern. (Nein es ist nicht der Anwender, sondern der Programmierer, der meine Funktion benutzt, meist ich selber).

    Nein. exceptionsicherheit hat nichts aber auch rein garnichts mit try/catch zu tun.

    Schau dir mal die codes in meinem artikel an - wieviele try/catch siehst du? Im idealfall hast du exakt 1 try/catch in deinem programm.



  • Ich kenne den Unterschied zwischen exceptionsicher und und Exceptions. Aber ich sollte doch ein Beispiel fuer 10 Resourcen ohne Verwendung von Exceptions fuer Tachyon liefern ... dat isses. Ja wenn sofort ein Rollback durchgefuehrt werden soll, dann ... muss ich drueber nachdenken. In dem Artikel kommen sehr wenig try/catch vor, aber ich bin da noch von dem anderen Artikel gepraegt ...

    Verstehe ich nicht. Wo ist der Unterschied zu Exceptions?

    Ich benutze keine und zwinge den Benutzer/Programmierer nicht dazu, welche zu benutzen. Wenn er dann eine Exception basierend auf der Rueckgabe werfen moechte, kann er gern machen.



  • knivil schrieb:

    Aber ich sollte doch ein Beispiel fuer 10 Resourcen ohne Verwendung von Exceptions fuer Tachyon liefern ...

    Dein Beispiel funktioniert aber nicht, bzw. macht keinen Sinn.


Anmelden zum Antworten