Fehlerbehandlung in einer Engine



  • 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?



  • Xebov schrieb:

    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.

    Du hast doppelten Code. Du musst an hundert stellen auf XX_FAILED testen und das doofe dabei: RunLoop liefert 0 bei Fehler. Tja. Dumme Sache. Sieht man halt einfach nicht.

    Der exception Code ist aber garantiert korrekt. (sofern die einzelnen funktionen korrekt sind).

    Teil 2 erschließt sich mir nicht wo sind denn da die Exceptions?

    Teil 1 ist ein Beispiel wie man es nicht macht. Du hast sehr korrekt die paralleln zu error codes gezogen.
    Deshalb macht man es aber wie in Teil 2.
    Das tolle an Teil 2 ist, dass es keine exceptions gibt die uns in den weg kommen. die fehlerbehandlung findet komplett woanders statt. Das ist das tolle daran.

    weil ich die Ifs bedeutend Lesbarer finde.

    Schau dir nochmal die Klasse Class an. Die 2. variante.
    Findest du sie nicht leserlicher als variante 1?

    guter code trennt fehlerbehandlung von logik. dadurch gewinnen wir code klarheit (ein if bringt keine klarheit, das if kann falsch sein oder etwas falsches implizieren), reduzieren code und koennen recht simpel fehlerfreiheit nachweisen.

    exceptions kann man uebrigens auch ignorieren - aber man muss es explizit machen. was deshalb sinnvoll ist, da es nun nicht passieren kann dass man einen fehler vergisst. wie oft pruefst du zB printf() auf erfolg? nie. das ist logisch, weil es ja nie fehlschlaegt. aber was wenn doch?
    und hier kommen die exceptions ins spiel. denn dann pruefst du ploetzlich immer ob printf erfolg hatte - und das mit 0 aufwand. du bekommst diese ueberpruefung geschenkt. und genau das ist das tolle.

    Xebov schrieb:

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

    Crashen ist gut. In undefinierten Zustand weiterlaufen ist schlecht.

    Logbuchfunktion, ein Funktionsaufruf mit etwas Text der in ein Logbuch egschrieben wird, fertig, dann kann man hinterher nachschaun wo das Problem genau lag

    Viel besser: ich lasse den Code selber darauf reagieren. Kostet mich null aufwand, ist ganz simpel polymorph loesbar.

    logen kann ich mit exceptions uebrigens deutlich besser als ohne. denn wir koennen ploetzlich generisch sagen ob eine funktion fehlgeschlagen ist oder nicht. wenn eine exception aktiv ist, gab es einen fehler. das ist mit error codes nicht moeglich.

    Xebov schrieb:

    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?

    Wenn ich 2 Bibliotheken verwende, kann ich nicht mehr generischen fehlerbehandlungscode schreiben - da ich nicht weiss ob ich in errno, GetLastError() oder WSAGetLastError() nachschauen soll. es sei denn ich wrappe alle fehlercodes, aber das ist unrealistisch.

    weiters ists sehr schwer korrekte error code translation zu machen. und man muss dauernd in die doku schauen: ist return==0 fehler oder return!=0? woher weiss ich das vorher? und wo stehen jetzt die weiteren details zum error? wenn ich SendSomeFileToClient() aufrufe, und es liefert einen fehler. ist der erweiterte fehlercode in GetLastError, WSAGetLastError oder errno zu finden?

    uU ist send() fehlgeschlagen, dann ists WSAGetLastError oder aber fopen() dann ists errno oder aber FileMap() dann ists GetLastError. Echt kompliziert - also muss ich es ploetzlich wrappen und wir muessen eine komplette infrastruktur aufbauen. klar kann man das machen. aber keiner machts. man ignoriert die extended fehler codes einfach.

    mit exceptions kannst du aber gratis und generisch darauf reagieren. und das ganze mit weniger denkaufwand und schreibaufwand 🙂

    Ich warte btw immer noch auf ein Argument gegen Exceptions.
    Also ich koennte da schon ein paar Punkte vorbringen wo Exceptions probleme machen...



  • Shade Of Mine schrieb:

    Du hast doppelten Code. Du musst an hundert stellen auf XX_FAILED testen und das doofe dabei: RunLoop liefert 0 bei Fehler. Tja. Dumme Sache. Sieht man halt einfach nicht.

    Wo hab ich da aber doppelten Code, wenn ich ne Funktion habe die ne datei laden soll und die schlägt Fehl kann ich ja genauso an eine Funktion weiterleiten die dafür zuständig ist was zu tun ist wenns der Fall ist und diese Funktion wiederrum kann üebrall aufgerufen werden, wäre also kein doppelter Code.

    Shade Of Mine schrieb:

    Teil 1 ist ein Beispiel wie man es nicht macht. Du hast sehr korrekt die paralleln zu error codes gezogen.
    Deshalb macht man es aber wie in Teil 2.
    Das tolle an Teil 2 ist, dass es keine exceptions gibt die uns in den weg kommen. die fehlerbehandlung findet komplett woanders statt. Das ist das tolle daran.

    Also nur auf die Gefahr hin das du mich missverstanden hattest mit Teil 2 meinte ich den unteren Abschnitt des 2. Quotes. Du sagst da sind einem keine Exceptions im Weg, das wirft bei mir die Frage auf wer wirft die denn dann? Ich meine das Was da steht ist ja im Grunde Code der keine Exceptions wirft. Oder übersehe ich da was?

    Shade Of Mine schrieb:

    Schau dir nochmal die Klasse Class an. Die 2. variante.
    Findest du sie nicht leserlicher als variante 1?

    Das komtm drauf an, der obere Codeabschnitt hat den klaren Vorteil das man sieht was fliegt wenns was zu fliegen gibt.

    guter code trennt fehlerbehandlung von logik. dadurch gewinnen wir code klarheit (ein if bringt keine klarheit, das if kann falsch sein oder etwas falsches implizieren), reduzieren code und koennen recht simpel fehlerfreiheit nachweisen.

    exceptions kann man uebrigens auch ignorieren - aber man muss es explizit machen. was deshalb sinnvoll ist, da es nun nicht passieren kann dass man einen fehler vergisst. wie oft pruefst du zB printf() auf erfolg? nie. das ist logisch, weil es ja nie fehlschlaegt. aber was wenn doch?

    Shade Of Mine schrieb:

    und hier kommen die exceptions ins spiel. denn dann pruefst du ploetzlich immer ob printf erfolg hatte - und das mit 0 aufwand. du bekommst diese ueberpruefung geschenkt. und genau das ist das tolle.

    Du spielst damit darauf an das man im Erfolgsfall keine Exception bekomt und somit die variante zählt keine Meldung=Erfolg? Das würde natürlich dafür sprechen.

    Shade Of Mine schrieb:

    Crashen ist gut. In undefinierten Zustand weiterlaufen ist schlecht.

    Naja was heist undefinierter Zustand? Wenn ich ne Datei laden will und sie lädt nicht dann gibt es früher oder später (meist früher) eine Access Violation und das wars, was schlimemres fällt mir gerade nicht ein, und ein einfaches abrauchen des programms ohne evtl Datenfreizugeben durch ne unhandled Exception finde ich nicht so prikelnd.

    Shade Of Mine schrieb:

    Viel besser: ich lasse den Code selber darauf reagieren. Kostet mich null aufwand, ist ganz simpel polymorph loesbar.

    logen kann ich mit exceptions uebrigens deutlich besser als ohne. denn wir koennen ploetzlich generisch sagen ob eine funktion fehlgeschlagen ist oder nicht. wenn eine exception aktiv ist, gab es einen fehler. das ist mit error codes nicht moeglich.

    Naja ob ich deswegen leichter Logen kann, so frage ich den Code ab und sage schreib das oder ebend as, und wenn man die Logbuch funktion entsprechend einbetet fliegt der Logbucheintrag im Idealfall sowieso direkt von der Stelle ab wo das Problem aufgetreten ist.

    Shade Of Mine schrieb:

    mit exceptions kannst du aber gratis und generisch darauf reagieren. und das ganze mit weniger denkaufwand und schreibaufwand 🙂

    Naja generisch ist ja toll, aber wenn jeder Exceptions in nem anderen Format loswirft mit anderem Inhalt muß ich trotzdem entsprechend einiges zusammenschreiben das einzige was sie gemeinsamm haben ist dannd as sie Exceptions benutzen, wenn man mehrere Bibliotheken über Errorcodes hat wo sich nur das ausgabeformat des errorcodes unetrscheidet kann man da ja eigentlich auch gut mit arbeiten.



  • Xebov schrieb:

    Naja was heist undefinierter Zustand? Wenn ich ne Datei laden will und sie lädt nicht dann gibt es früher oder später (meist früher) eine Access Violation und das wars, was schlimemres fällt mir gerade nicht ein, und ein einfaches abrauchen des programms ohne evtl Datenfreizugeben durch ne unhandled Exception finde ich nicht so prikelnd.

    Dir ist schon klar, dass beim stack-unwinding alles sauber aufgeräumt wird?



  • Xebov schrieb:

    Wo hab ich da aber doppelten Code

    Du musst aber trotzdem jede Funktion, die du aufrufst, auf Fehler prüfen, während du mit Exceptions viel Code auf einmal abdecken kannst und trotzdem (oder sogar noch besser) spezifische Exception-Informationen erhältst.

    Xebov schrieb:

    Naja was heist undefinierter Zustand? Wenn ich ne Datei laden will und sie lädt nicht dann gibt es früher oder später (meist früher) eine Access Violation und das wars, was schlimemres fällt mir gerade nicht ein, und ein einfaches abrauchen des programms ohne evtl Datenfreizugeben durch ne unhandled Exception finde ich nicht so prikelnd.

    Dir ist es also lieber, wenn dein Programm fünf Minuten später aus irgendeinem Grund abstürzt? 😮

    1. Viel Spass beim Lokalisieren des Fehlers.
    2. Wer sagt, dass es zu einer Access Violation kommt? Das Schlimme ist ja, dass das Verhalten undefiniert ist. Du kannst also ohne Weiteres wichtige Daten überschreiben, das Programm läuft unbehelligt weiter. Wenn es schlecht läuft, werden die beschädigten Daten sogar noch weitergetragen, zum Beispiel in Dateien gespeichert oder über Server geschickt.
    3. Datenfreigabe wird ja gerade einfach durch Exceptions, weil RAII ins Spiel kommt. Auch etwas, das beim Weiterarbeiten nicht gewährleistet ist.

    Xebov schrieb:

    Naja generisch ist ja toll, aber wenn jeder Exceptions in nem anderen Format loswirft mit anderem Inhalt muß ich trotzdem entsprechend einiges zusammenschreiben

    Wenn man sich als Bibliothekprogrammierer bei den Exceptions was überlegt, gliedert man diese in die Standard-Exception-Hierarchie ein. Somit kann man sowohl als Werfer als auch als Fänger soweit abstrahieren wie nötig.



  • Helferlein, Ein schrieb:

    Dir ist schon klar, dass beim stack-unwinding alles sauber aufgeräumt wird?

    Das hatte ich beim lesen des Artikels aber etwas anders verstanden, der Suggerierte eher ein ableben nach dem Motto und Tschüß ohne aufzuräumen, dh inkl Leaks im Speicher und in Com Objekten.

    Nexus schrieb:

    Dir ist es also lieber, wenn dein Programm fünf Minuten später aus irgendeinem Grund abstürzt? 😮

    1. Viel Spass beim Lokalisieren des Fehlers.
    2. Wer sagt, dass es zu einer Access Violation kommt? Das Schlimme ist ja, dass das Verhalten undefiniert ist. Du kannst also ohne Weiteres wichtige Daten überschreiben, das Programm läuft unbehelligt weiter. Wenn es schlecht läuft, werden die beschädigten Daten sogar noch weitergetragen, zum Beispiel in Dateien gespeichert oder über Server geschickt.
    3. Datenfreigabe wird ja gerade einfach durch Exceptions, weil RAII ins Spiel kommt. Auch etwas, das beim Weiterarbeiten nicht gewährleistet ist.

    1. Der fehler wäre ja aber mit nem Logbuch nicht schwer zu fidnen, wenn Funktion x etwas tun soll und es schlägt Fehl, dann würde sie einfach ins Logbuch schreiben was los war, dh ein Absturz ja, aber der Fehler würde sich sehr leicht finden lassen.
    2. Soweit das die daten irgendwohin gehen kommt es bei mir zzt (noch) nicht, mein größter feind sind leere Poinetr weil dateien nicht an ihrem Platz sind.
    3. Dazu hab ich bisher keine befriedigende Antwort im Netz gefunden was das ganze eigentlich tut.



  • Xebov schrieb:

    Helferlein, Ein schrieb:

    Dir ist schon klar, dass beim stack-unwinding alles sauber aufgeräumt wird?

    Das hatte ich beim lesen des Artikels aber etwas anders verstanden, der Suggerierte eher ein ableben nach dem Motto und Tschüß ohne aufzuräumen, dh inkl Leaks im Speicher und in Com Objekten.

    Also, falls dir das noch nicht klar ist:
    Bei einer Access-Violation, ausgelöst durch einen ignorierten Error-Code, räumt nichts auf. Das Programm "crashed". Bei einer Exception werden alle RAII Objekte aufgeräumt. Solange man also seine Ressourcen in RAII-Klassen verwaltet (was in C++ ja sowieso Standard ist), werden diese auch bei einer "durchfliegenden" Exception alle aufgeräumt.

    Xebov schrieb:

    1. Der fehler wäre ja aber mit nem Logbuch nicht schwer zu fidnen, wenn Funktion x etwas tun soll und es schlägt Fehl, dann würde sie einfach ins Logbuch schreiben was los war, dh ein Absturz ja, aber der Fehler würde sich sehr leicht finden lassen.
    2. Soweit das die daten irgendwohin gehen kommt es bei mir zzt (noch) nicht, mein größter feind sind leere Poinetr weil dateien nicht an ihrem Platz sind.
    3. Dazu hab ich bisher keine befriedigende Antwort im Netz gefunden was das ganze eigentlich tut.

    zu 1.) Das Problem ist, in einer LoadFile-Funktion kannst du zwar ins Logbuch schreiben "Datei XY konnte nicht geöffnet werden". Aber das bringt dir nicht wirklich viel. Erst auf einer höheren Ebene kann man angemessen reagieren, indem man z.B. eine Default-Datei erstellt, den Benutzer informiert, dass die angegebene Datei nicht existiert, oder das Programm abbricht, weil entscheidende Ressourcen fehlen, etc. Einfach nur zu loggen "Datei konnte nicht geöffnet werden" ist imho eine schlechte Fehlerbehandlung.

    zu 2.) und 3.) ? Kannst du, dass etwas weiter erläutern? Das kommt so rüber, als seist du noch ein blutiger Anfänger, könnte aber einfach an der schlechten Formulierung liegen.

    Gruß
    Don06



  • Don06 schrieb:

    zu 2.) und 3.) ? Kannst du, dass etwas weiter erläutern? Das kommt so rüber, als seist du noch ein blutiger Anfänger, könnte aber einfach an der schlechten Formulierung liegen.

    ähm ja natürlich kann ich das. 2. ist so zu verstehen, ich kann mir im Moment keinen Fehler vorstellen der mehr als ne Access Violation durch den Raum fliegen lassen kann, was schlimeres, oder anderes ist mir bisher nie passiert, deswegen fehlt mir schlicht die Vorstellung welche schlimmen fehler noch so auftreten können
    3. ist folgendermaßen gemeint der Begriff RAII sagt mir erstmal garnichts, ich hab das ganze zwar in den Artikeln mit gelesen jedoch im Netz noch nichts gefunden was mir das ganze wirklich näher bringen kontne.

    Anfänger? jain, ich Programmiere noch nicht so lange und eigne mir Sachen an wie ich sie brauche, dh ich kenne einiges aber eben nicht alles.



  • Was haltet ihr davon?

    Anstatt das Problem mit unnötigen Exceptions wie FileNotFound zu lösen, die ja gar keine wirklichen 'Ausnahmen' darstellen, also so:

    class TextureManager
    {
       /**
        * @throw FileNotFoundError if the file was not found.
        */
       TexturePtr load(const std::string& filename);
    }
    

    könnte man es ja wie in Java machen: einfach vom Benutzer der Klasse erwarten, dass er vorher überprüft ob die Datei lesbar ist und falls er das versäumt, also einen programmiertechnischen Fehler macht, einen runtime_error werfen, der ja eine wirkliche Ausnahme wegen programmierfehler darstellt.

    class TextureManager
    {
       bool exists(const std::string& filename);
    
       /**
        * You have to make sure that the file exists.
        * exists() can be used to check that.
        * @see exists
        * @throw std::runtime_error if the file was not found.
        */
       TexturePtr load(const std::string& filename);
    }
    

    Die Benutzung wäre dann auch schön nett ohne try-catch, den den runtime_error erwartet man ja nicht, da er eine Ausnahme darstellt:

    void fuzzy() {
       if(textureManager->exists(filename)) {
          texture = textureManager->load(filename);
       } else {
          std::cout << "Can't open texture in fuzzy code." << std::endl;
       }
    }
    

    Nachteile:
    - Performance, denn die Datei müsste im Grunde genommen 2 mal geöffnet werden.
    - Dezentralisierung der Fehlerbehandlung, jede klasse fuzzy, wuzzy, buzzy usw. reagiert unabhänig auf den Fehler, dass eine Datei nicht gefunden wurde und somit -> Codeverdopplung. (?)



  • Du hättest ein Problem, wenn die Datei existiert aber du keine Rechte zum öffnen hast. Oder sie wird zwischen exists(...) und load(...) gelöscht 😉

    edit: Aber abgesehen davon, dass das Dateisystem-Beispiel ungünstig gewählt wurde, wäre ein Runtime-Error und/oder ein assert angebracht, wenn eine Pre-Condition nicht eingehalten wurde.



  • Badestrand schrieb:

    Du hättest ein Problem, wenn die Datei existiert aber du keine Rechte zum öffnen hast. Oder sie wird zwischen exists(...) und load(...) gelöscht 😉

    Zu 1.: Dann nennen wir exists eben um in existsAndIsReadable oder bauen uns noch isReadable dazu.
    Zu 2.: Da muss ich dir recht geben. Das ist ganz klar eine knallharte Schwäche des ganzen.
    Hast du einen Gegenvorschlag?

    Vielleicht wäre es am besten die Datei einfahc selbst mit std::fstream zu öffnen und dann per std::fstream& and readTexture oder sowas weiterzureichen.
    Dann muss man als Benutzer natürlich die Flags von fstream überprüfen und readTexture wirft einem runtime_error falls die nicht stimmen.



  • Badestrand schrieb:

    [...]
    edit: Aber abgesehen davon, dass das Dateisystem-Beispiel ungünstig gewählt wurde, wäre ein Runtime-Error und/oder ein assert angebracht, wenn eine Pre-Condition nicht eingehalten wurde.

    Wie meinst du das? Ich habe doch oben angegeben, dass ein runtime_error geworfen wird, wenn die pre condition nicht stimmt. Oder ist das nur als Bestätigung gemeint?
    Wieso ist das Beispiel schlecht gewählt, es ist doch extra so gewählt, dass man sich fragen muss ob man es mit Ausnahmen(=exceptions) zu tun hat wenn eine Datei nicht gefunden wurde?
    Es gibt natürlich Fälle bei dennen klar ist, dass Exceptions perfekt sind und solche bei dennen klar ist, dass sie fehl am platz sind. Das hier soll ja absichtlich ein schweres Beispiel sein.



  • fuzzy-wuzzy-bear schrieb:

    Hast du einen Gegenvorschlag?

    Ich sag ja, das Beispiel ist einfach schlecht gewählt 😉 Genau das, was du hier skizziert hast, gibt's ja auch in der Standard-Bibliothek:

    std::vector<int> nums = ...;
    size_t index = ...;
    if ( index < nums.size() )         // Sowas wie exists(..), nur halt ob Index in Range
        cout << nums.at(index) << "\n";   // Zugriff, mit sonstwas-exception bei falschem Index
    


  • fuzzy-wuzzy-bear schrieb:

    Wie meinst du das? Ich habe doch oben angegeben, dass ein runtime_error geworfen wird, wenn die pre condition nicht stimmt. Oder ist das nur als Bestätigung gemeint?

    Das Problem mit der Datei-existiert Pre-Condition hier ist, dass du sie als Aufrufer nicht mit 100% Sicherheit erfüllen kannst. Eben weil OS und Treiber und Hardware als unberechenbare Faktoren dazwischen liegen. Bei dem vector-Beispiel kannst du beim at(...) -Aufruf sicher sein, dass der Index "in Range" ist, wenn du vorher mit index<v.size() prüfst (es sei denn, in einem anderen Thread wird mit einer Referenz auf den vector genau dieser verändert). Wenn eine Pre-Condition verletzt wird, hast du beim Programmieren einen Fehler gemacht. Wenn eine Exception fliegt, tritt nur ein unwahrscheinlicher Fall ein, was aber kein Programmfehler ist.

    fuzzy-wuzzy-luzzy-bear schrieb:

    Wieso ist das Beispiel schlecht gewählt, es ist doch extra so gewählt, dass man sich fragen muss ob man es mit Ausnahmen(=exceptions) zu tun hat wenn eine Datei nicht gefunden wurde?
    Es gibt natürlich Fälle bei dennen klar ist, dass Exceptions perfekt sind und solche bei dennen klar ist, dass sie fehl am platz sind. Das hier soll ja absichtlich ein schweres Beispiel sein.

    Ist es auch 🙂 std::ifstream bzw ofstream regeln das ja mit internen Fehler-Zuständen, die man abfragen kann.

    Ich würde an die "Exception oder nicht Exception"-Frage anders herangehen, nämlich was der Nutzer der Funktion wohl erwarten würde. Und das hängt von dem Rahmen, vom Namen und von den Parametern der Funktion ab.
    Während man bei bool tryLoadTexture( const std::string& filename, TexturePtr& tex ) klar erwartet, dass der Rückgabewert den Erfolg zurückgibt und keine Exception fliegt, würde ich bei TexturePtr loadTexture( filename ) definitiv im Fehlerfall Exceptions erwarten. Auch würde ich bei keiner der Funktionen Pre-Conditions erwarten.


Anmelden zum Antworten