Fehlerbehandlung in einer Engine



  • knivil schrieb:

    aber ich bin da noch von dem anderen Artikel gepraegt ...

    Dann wird es Zeit dass du dir modernes Exceptionhandling ansiehst und mir Gründe nennst warum error Codes besser sind.

    Vorallem solltest du mal darüber meditieren:

    Es gibt 3 Probleme mit diesem Code.

    1. Die Fehlerbehandlung erfolgt lokal und macht dadurch den Code schwerer lesbar.
    2. Jede Funktion verlangt andere Behandlung von Fehlern.
    3. In Sprachen ohne Garbage-Collector muss man seinen eigenen Mist selbst wegräumen. Dieser Cleanup-Code ist kompliziert einzubauen. Hier z.B. über hässliche gotos gelöst. Alternativ auch mit tief verschachtelten ifs lösba

    Punkt 3 ist mit RAII lösbar, aber Punkt 1 und 2 liegen schwer.



  • What the fuck ? So (aehnlich) arbeite ich staendig. Ich muss zugeben, dass ich mir nicht die Muehe gemacht habe ein schoen formatiertes Beispiel mit C++ Syntax zu basteln. Sorry.

    Error Codes besser/schlechter ... das ist so eine Ansichtssache. Ich finde Exceptions restriktiver. Mom, ich suche mal nen Zitat ... finde es grad nicht, sorry



  • knivil schrieb:

    Ich finde Exceptions restriktiver

    Gründe bitte.



  • Ich zwinge den Benutzer meiner Funktion auch Exceptions zu benutzen. Er kann nicht auf Exceptions verzichten.



  • knivil schrieb:

    Ich zwinge den Benutzer meiner Funktion auch Exceptions zu benutzen. Er kann nicht auf Exceptions verzichten.

    Und die Alternative: Erzwingen lokaler Fehlerbehandlung durch das Verwenden von Error-Codes findest du weniger restriktiv?



  • Helferlein, Ein schrieb:

    knivil schrieb:

    Ich zwinge den Benutzer meiner Funktion auch Exceptions zu benutzen. Er kann nicht auf Exceptions verzichten.

    Und die Alternative: Erzwingen lokaler Fehlerbehandlung durch das Verwenden von Error-Codes findest du weniger restriktiv?

    knivil, dabei könntest du es dir doch so einfach machen. Es gibt nämlich ein Argument gegen Exceptions und für Error-Codes. Ich gehe soweit und sage sogar es ist das Argument gegen Exceptions.



  • knivil schrieb:

    Ich zwinge den Benutzer meiner Funktion auch Exceptions zu benutzen. Er kann nicht auf Exceptions verzichten.

    Genauso zwingst du den Benutzer deiner Funktion lokal auf die Fehler zu reagieren. Er kann nicht auf error codes verzichten.

    Deshalb: sag mal n ordentliches argument gegen exceptions



  • Helferlein, Ein schrieb:

    Und die Alternative: Erzwingen lokaler Fehlerbehandlung durch das Verwenden von Error-Codes findest du weniger restriktiv?

    Soll er doch ne Exception werfen basierend auf den Fehlercode. Prinzipiell habe ich nichts gegen Exceptions, nur werden sie haeufig an falscher Stelle eingesetzt.

    Ich soll 's mir so einfach machen? Hoert auf mich zu noetigen und Generalisieren werde ich nicht! Exceptions ueber Bibliotheksgrenzen hinweg sind einfach scheisse und alles was damit zusammenhaengt. Aber das 'nen generelles Problem von C++.



  • Da is man mal ne Studne nicht da und hat gleich richtig was zu lesen.

    Shade Of Mine schrieb:

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

    versus

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

    Ich habe jetzt mal den Code zusammenkopiert. Zum oberen Teil, was wäre dann dem If so evrwerflich? man sieht auf einen Blick was Sache ist und was passiertw enn was passiert, option nr2 ohne die Ifs steht einem ja auch noch offen wenn man sich 100% sicher ist das es so klappen kann.

    Shade Of Mine schrieb:

    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); 
      } 
    //... 
    };
    

    Ja sowas im drehe meine ich das hab ich gelesen. Was ich an dem Beispiel auszusetzen hätte wäre das man das selbe was da ind em Try Block und dem Katch passiert auch über ne einafche if Abfrage lösen könnte, ich meine es klappt doer nicht und die Variante if und errorcode würde das selbe bewirken, der Anwender könnte ja genauso abfragen obe r errrorcode geliefert wurde und dann an eine Funktion verweisen die das Problem beseitigt, damit wäre dann die Fehlerbehandlung auch ausgelagert. Teil 2 erschließt sich mir nicht wo sind denn da die Exceptions?

    Mir fehlt nachwievor ein guter Grund die sachen einzusetzen, ich habe hier zB gerade ein Gedankenspiel, ich habe einen Texturmanager der Texturen lädt und verwaltet. Wenn eine Textur nicht geladen werden kann kann ich genauso einen errorcode abgeben der sagt das teil kontne nicht geladen werden, was der Nutzer damit macht wäre ja egal, ich sehe da in Exceptions keinen Vorteil, nur den Nschteil das ich sie nicht ignorieren darf. Eine Ordliche fehlerbehandlung müsste ich auch nichtmal durchführen, denn in dem Moment wo mir ein Errorcode entgegen komtm kann ich genauso auf eine Funktion verweisen die zB eine Standarttextur lädt die für diesen Fall vorgesehen ist, das geht mit Exceptions genauso, sie liefern mir also Gedanklich keinen vorteil, nur Nachteile weil ich die Ifs bedeutend Lesbarer finde.



  • knivil schrieb:

    Helferlein, Ein schrieb:

    Und die Alternative: Erzwingen lokaler Fehlerbehandlung durch das Verwenden von Error-Codes findest du weniger restriktiv?

    Soll er doch ne Exception werfen basierend auf den Fehlercode. Prinzipiell habe ich nichts gegen Exceptions, nur werden sie haeufig an falscher Stelle eingesetzt.

    Ich soll 's mir so einfach machen? Hoert auf mich zu noetigen und Generalisieren werde ich nicht! Exceptions ueber Bibliotheksgrenzen hinweg sind einfach scheisse und alles was damit zusammenhaengt. Aber das 'nen generelles Problem von C++.

    Aber das gleiche kann er doch auch mit den Exceptions tun, einfach Fangen und dafür einen Fehlercode zurückgeben.

    Mit deinem letzten Satz bist du dem Argument von dem ich weiter oben sprach schon recht nahe, aber noch nicht ganz.



  • Aber das gleiche kann er doch auch mit den Exceptions tun, einfach Fangen und dafür einen Fehlercode zurückgeben.

    Ja genau, der keinen Exceptioncode schreiben moechte, muss es trotzdem tun ... Und anfangen zu raten werde ich hier bestimmt nicht.



  • knivil schrieb:

    Aber das gleiche kann er doch auch mit den Exceptions tun, einfach Fangen und dafür einen Fehlercode zurückgeben.

    Ja genau, der keinen Exceptioncode schreiben moechte, muss es trotzdem tun ... Und anfangen zu raten werde ich hier bestimmt nicht.

    Ja genau, der keinen Error-Code-Quellcode schreiben moechte, muss es trotzdem tun ...

    Das führt doch zu nichts. Aber wie gesagt es gibt einen Grund warum der Einsatz von Error-Codes besser sein kann: wenn die Bibliothek nicht ausschließlich für C++ gedacht ist.


  • Administrator

    @knivil,
    Ok, vielleicht mal ein paar einfachere Argumente, wieso ich mich gegen Fehlercodes entschieden habe:
    1. Was passiert wenn ein Fehler ignoriert wird:
    Bei der Exception, fliegt sie durch, das Programm wird terminniert.
    Beim Fehlercode, läuft das Programm weiter, bis irgendwo wahrscheinlich ein fataler Fehler auftritt und das Programm heftig abstürzt, womöglich aber vorher noch Daten korumpiert.
    2. Was passiert, wenn man mit verschiedenen Bibliotheken in einer stark verschachtelten Funktionskette arbeitet. Man möchte am Ende der Kette erfahren, was für ein Fehler auftrat:
    Bei Exceptions, müssen einfach nur alle möglichen Exception zuoberst über einen try-catch Block gefangen werden. Perfekt.
    Bei Fehlercodes, muss man nicht nur eine Übereinstimmung der Datentypen haben, sondern dann auch noch irgendwie erfahren, von welcher Bibliothek nun der Fehlercode kam, denn wahrscheinlich ist der Wert, der jeweiligen Fehlercodes noch identisch. Ergo, ich kann es nicht mehr herausfinden. Ich muss aufeinmal an die Funktionen Objekte mitgeben, welche die Fehler mitloggen sollen. Die Funktionen machen dadurch nicht nur Dinge, welche sie eigentlich gar nichts angeht, sondern sie werden auch noch aufgeblasen und komplex zu lesen.

    Zudem möchte ich mich noch selber zitieren:

    Dravere schrieb:

    Wenn man trotzdem Fehlercodes anbieten möchte, dann schlage ich eine Überladung der Funktion vor, wo man eine Variable übergeben kann, wo dann der Fehlercode reingespeichert wird. Die Funktion hat dann eine nothrow Garantie.

    Oder vielleicht das ganze in Code:

    void foo(); // Kann eine Exception werfen
    
    void foo(myengine::ErrorCode& errCode)
    {
      try
      {
        foo();
        errCode = myengine::NO_ERR;
      }
      catch(myengine::Exception& err)
      {
        errCode = err.get_code();
      }
    }
    

    Typisches Beispiel für so eine Schnittstelle, findet man bei Boost.Asio.

    Grüssli



  • zu 1) Du hast einen lausigen Programmierer.
    zu 2) In die Logdatei schauen. Ich wuerde keine inout-Parameter einer Funktion fuer Fehleridentifizierung mitgeben. Desweiteren kann der Fehlerort mit Exceptions nicht eindeutig bestimmt werden. Was hilft dir ein out_of_range Exception?

    Fehler sollten entweder durch Exceptions oder "Fehlercodes" gehandhabt werden, ein Mischmasch halte ich nicht fuer sinnvoll.



  • Dravere schrieb:

    Ok, vielleicht mal ein paar einfachere Argumente, wieso ich mich gegen Fehlercodes entschieden habe:
    1. Was passiert wenn ein Fehler ignoriert wird:
    Bei der Exception, fliegt sie durch, das Programm wird terminniert.
    Beim Fehlercode, läuft das Programm weiter, bis irgendwo wahrscheinlich ein fataler Fehler auftritt und das Programm heftig abstürzt, womöglich aber vorher noch Daten korumpiert.

    Und was ist wenn die Exception einafch durch einen fehelr oder Unwissenheit des Benutzers nicht abgefangen wurde? Dann crasht das Programm trotzdem.

    Dravere schrieb:

    2. Was passiert, wenn man mit verschiedenen Bibliotheken in einer stark verschachtelten Funktionskette arbeitet. Man möchte am Ende der Kette erfahren, was für ein Fehler auftrat:
    Bei Exceptions, müssen einfach nur alle möglichen Exception zuoberst über einen try-catch Block gefangen werden. Perfekt.
    Bei Fehlercodes, muss man nicht nur eine Übereinstimmung der Datentypen haben, sondern dann auch noch irgendwie erfahren, von welcher Bibliothek nun der Fehlercode kam, denn wahrscheinlich ist der Wert, der jeweiligen Fehlercodes noch identisch. Ergo, ich kann es nicht mehr herausfinden. Ich muss aufeinmal an die Funktionen Objekte mitgeben, welche die Fehler mitloggen sollen. Die Funktionen machen dadurch nicht nur Dinge, welche sie eigentlich gar nichts angeht, sondern sie werden auch noch aufgeblasen und komplex zu lesen.

    Logbuchfunktion, ein Funktionsaufruf mit etwas Text der in ein Logbuch egschrieben wird, fertig, dann kann man hinterher nachschaun wo das Problem genau lag und kann sogar noch richtig viele Informationen drin zurücklassen. Was nützt es denn wenn man ne Kette aus 20 Funktionen hat und am ende erfährt das die Funktion nr10 absolut in die Hose ging? Wenn die Funktion zB Teile aus eienr Datei laden soltle dann wäre das Ladend er Datei fehlgeschlagen, ob es an diesem teil lag wäre nebensächlichd enn auch das wissen wo genau es geschah im Programm würde dir ja im Zweifel nicht unbedingt weiterhelfen, mehr als ein Daten Korrupt könntest du nicht ausgeben, da wäre die Exception im Grudne genauso aussagefähig wie ein errorcode der einfach sagt Fehler beim lesend er datei und ein Logbuch hilft dir am Ende ja eh mehr weil du darin genau siehst was sache war.



  • knivil schrieb:

    Fehler sollten entweder durch Exceptions oder "Fehlercodes" gehandhabt werden, ein Mischmasch halte ich nicht fuer sinnvoll.

    👍

    knivil schrieb:

    Was hilft dir ein out_of_range Exception?

    Imho nichts, solche Sachen sollten meiner Meinung nach durch asserts geregelt werden (also Fehler, die nicht auftreten dürften). Eine FileNotFoundException nützt aber schon eher 🙂

    Xebov schrieb:

    Und was ist wenn die Exception einafch durch einen fehelr oder Unwissenheit des Benutzers nicht abgefangen wurde? Dann crasht das Programm trotzdem.

    Was gut ist! Nichts ist schlimmer als unbemerkte Fehler.



  • knivil schrieb:

    zu 1) Du hast einen lausigen Programmierer.
    zu 2) In die Logdatei schauen. Ich wuerde keine inout-Parameter einer Funktion fuer Fehleridentifizierung mitgeben. Desweiteren kann der Fehlerort mit Exceptions nicht eindeutig bestimmt werden. Was hilft dir ein out_of_range Exception?

    Fehler sollten entweder durch Exceptions oder "Fehlercodes" gehandhabt werden, ein Mischmasch halte ich nicht fuer sinnvoll.

    Also mein Compiler besitzt tolle Makros mit denen ich die Datei, Funktion und aktuelle Quellcodezeile ermitteln kann und schon weiß ich ganz genau wo die Exception erzeugt wurde.

    Ich habe hier schon mehrfach als Pro-Argument für Error-Codes gelesen, dass man mühelos den Fehler ignorieren kann und dann kommst du jetzt plötzlich damit, dass man bei Exceptions nicht wüsste wo die Exception erzeugt wurde (ok, ich glaube das Argument kam nie von dir)? In meinem Debugger kann ich sogar einstellen, dass er bei jeder fliegenden Exception unterbricht!


  • Administrator

    knivil schrieb:

    zu 1) Du hast einen lausigen Programmierer.

    Der Super-Programmierer gibt es nur in Märchen. Ehm, moment mal, den gibt es nicht mal dort 🙂
    Ein Programmierer ist ein Mensch (das würden zwar gewisse Leute abstreiten, aber lassen wir das mal so stehen). Ein Mensch macht Fehler, ein Mensch übersieht Dinge. Wenn das mal passiert, dann bist du froh, dass dein Programm terminiert wurde und nicht die ganze Datenbank gelöscht hat 🙂

    knivil schrieb:

    zu 2) In die Logdatei schauen. Ich wuerde keine inout-Parameter einer Funktion fuer Fehleridentifizierung mitgeben.

    Und wo, bzw. wie, kommt der Fehler in die Logdatei? Und wie soll das Programm dort reinschauen? Und wie kannst du alle Fehlercodes der verschiedenen Bibliotheken mit deiner Logdatei vereinheitlichen, ohne zum Beispiel Fehlerbehandlungscode zu multiplizieren.
    Wie trennst du Fehlerbehandlungscode von Ausführungscode?

    Und vielleicht noch ein Zusatz für Xebov:
    Eine Exception kann ein Objekt sein. In einem Objekt kannst du verdammt viele Informationen reinstecken.

    knivil schrieb:

    Desweiteren kann der Fehlerort mit Exceptions nicht eindeutig bestimmt werden. Was hilft dir ein out_of_range Exception?

    Ich habe nie gesagt, dass es um den Fehlerort geht! Lies bitte was ich geschrieben habe. Es geht nämlich um sowas:

    try
    {
      tief_verschachtelte_funktionskette();
    }
    catch(myengine::Exception& err)
    {
      // ...
    }
    catch(boost::system::system_error& err)
    {
      // ...
    }
    catch(bliblablublib::Exception& err)
    {
      // ...
    }
    

    Die Behandlung der Fehler ist über die Bilbiotheken hinweg immer gleich. Eine Exception wird gleich geworfen und gleich gefangen. Wie man Fehlercodes realisiert hat dagegen überhaupt keine Standardisierung. Vielleicht auch nochmals Code zu den Fehlercodes:

    namespace myengine
    {
      enum ErrorCodes
      {
        NO_ERROR = 1,
        FATAL_BLI_BLU,
        IDIO_FOO_FOO,
        USW,
      }
    }
    
    namespace boost
    {
      typedef std::size_t const ErrorCode;
    
      ErrorCode const FATAL_SUPER_DUPPER_ERROR = 1; // Gleicher Wert wie myengine::NO_ERROR ;)
      // usw.
    }
    
    namespace bliblablublib
    {
      class ErrorCode
      {
        int m_code;
        std::string m_what;
      }
    
      // Und hier werden halt Objekte zurückgegeben.
    }
    

    Das ist ein völliges Wirrwarr. Jede Bibliothek führt seine eigenen Codes und anderes ein. Bei Exception ist das kein Problem, da der Exception-Mechanismus standardisiert ist.

    knivil schrieb:

    Fehler sollten entweder durch Exceptions oder "Fehlercodes" gehandhabt werden, ein Mischmasch halte ich nicht fuer sinnvoll.

    Das sagst gerade du, der sich darüber ärgert, dass man den Nutzer einschränkt. Du bist der restriktivste Mensch überhaupt 😃

    Grüssli



  • knivil schrieb:

    Ich zwinge den Benutzer meiner Funktion auch Exceptions zu benutzen. Er kann nicht auf Exceptions verzichten.

    Du zwingst den Benutzer, sich an des Interface Deiner API zu halten. Ich finde das gut. Mir ist mal zu Ohren gekommen, man sollte einem Benutzer eines Interfaces so wenig Möglichkeiten zur Falschbenutzung wie möglich an die Hand geben. Vielleicht ist diese Vorgehensweise inzwischen ja auch obsolete.
    Bei Exceptions muss man bewusst dafür sorgen, dass sie verschluckt werden. Man muss dann auch mit den Konsequenzen leben. Bei Fehlercodes kann das Verschlucken einfach so passieren, weil man die Prüfung vergisst. Da muss man dann erstmal herausbekommen, das es Konsequenzen gibt.

    Ich bin zwar auch öfter mal nicht Shades Meinung (oder wir schreiben aneinander vorbei), aber sein Artikel über Exceptionhandling und -Sicherheit (vor allem letzteres) ist sehr gut. Ihr solltet Euch das vielleicht nochmal zu Gemüte führen.



  • Dravere schrieb:

    Und wo, bzw. wie, kommt der Fehler in die Logdatei? Und wie soll das Programm dort reinschauen? Und wie kannst du alle Fehlercodes der verschiedenen Bibliotheken mit deiner Logdatei vereinheitlichen, ohne zum Beispiel Fehlerbehandlungscode zu multiplizieren.
    Wie trennst du Fehlerbehandlungscode von Ausführungscode?

    Und vielleicht noch ein Zusatz für Xebov:
    Eine Exception kann ein Objekt sein. In einem Objekt kannst du verdammt viele Informationen reinstecken.

    Ich würde sagen er kommt in die Logdatei weil die bibliotek/Engine oder was auch immer Sie dort hineinschreibt, gut bei mehreren Bibliotheken hätte man da evtl kleienre Probleme mit der Menge der Dateien die daraus entstehen würden, aber auch sowas wäre lösbar und die Logs können ja auch so designed werden das man sie einafch abstellen kann.

    Dravere schrieb:

    Das ist ein völliges Wirrwarr. Jede Bibliothek führt seine eigenen Codes und anderes ein. Bei Exception ist das kein Problem, da der Exception-Mechanismus standardisiert ist.

    Na das Beispiel fidne ich jetzt aber etwas hinkend, wenn ich Fehlercodes in einer Bibliothek benutze und die in ein Enum mit Namen Packe dann weis ich ja welchen typs der Fehelrcode ist damit kann eine Fehlinetrpretation ja kaum noch passieren oder solte ich mich da irren?


Anmelden zum Antworten