Catch Abfragen zusammenfassen



  • Die Exceptions die bisher im Quelltext sind weisen alle auf Fehler wie fehlende Dateien usw. hin, die verhindern, dass das programm überhaupt funktionieren kann. Der Benutzer soll eine Fehlermeldung erhalten, die aussagekräftig genug ist, dass er den Fehler melden oder selbst korrigieren kann.

    z.B.

    Die Datei <Dateipfad> konnt nicht gefunden werden.

    Dafür habe ich z.B. eine Überklasse eFileError, die einen String, nämlich den jeweiligen Pfad beinhaltet und davon erbt in diesem Fall die Klasse eFileNotFound. So kann ich exakte Fehlerprotokolle und Ausgaben erstellen, in denen auch die Position des Fehlers, bzw. der Pfad der fehlenden Datei steht. Wenn ich nur:

    throw "Datei nicht gefunden";
    

    Verwende, hilft mir das wenig, da zu Programmbeginn ein ganzer Haufen Dateien geöffnet werden. Anders fällt mir keine Möglichkeit ein, Fehlergrund und Faktoren zu übertragen.



  • Eine Datei, nicht nicht gefunden wurde ist kein Grund für eine Exception. Das ist völlig normal. Dein Programm wird doch auch damit klar kommen, wenn ein File nicht vorhanden ist, oder?
    Wenn ich eine Datei Suche, dann beendet sich der Explorer ja auch nicht, wenn er keine findet. 😉


  • Administrator

    drakon schrieb:

    Eine Datei, nicht nicht gefunden wurde ist kein Grund für eine Exception. Das ist völlig normal. Dein Programm wird doch auch damit klar kommen, wenn ein File nicht vorhanden ist, oder?

    Kommt wohl ein wenig darauf an, was für ein File das ist. Du kannst auch kein Spiel starten, wenn alle Daten von diesem Spiel nicht mehr vorhanden sind. Dann wäre das durchaus ein Grund für eine Exception 😉

    Grüssli



  • Exakt, es handelt sich um die Sprites usw. für ein Spiel. Es wäre ziemlich blöd wenn das Spiel normal startet und ein Einheitentyp unsichtbar wäre, weil kein Sprite vorhanden ist oder eventuell auch einfach alles abstürzt beim Versuch den nicht vorhandenen Sprite darzustellen.



  • drakon schrieb:

    Eine Datei, nicht nicht gefunden wurde ist kein Grund für eine Exception.

    Ich finde schon. Ist zum Beispiel eines der klassischen Beispiele für Exceptions, wenns um Exception vs. Assertion geht. Weil es sich eben um einen echten Laufzeitfehler handelt.

    Irgendwie musst du den Fehler ja behandeln, und wenn nicht mit Exceptions, dann eben mit Returncodes oder sonst was. Exceptions sind aber durchaus praktisch in dem Kontext, eben weil man sie durchkrachen lassen und an zentraler Stelle behandeln kann.

    Little Programmer schrieb:

    Exakt, es handelt sich um die Sprites usw. für ein Spiel. Es wäre ziemlich blöd wenn das Spiel normal startet und ein Einheitentyp unsichtbar wäre, weil kein Sprite vorhanden ist oder eventuell auch einfach alles abstürzt beim Versuch den nicht vorhandenen Sprite darzustellen.

    Das ist ja immer noch kein Grund für so eine Masse an unterschiedlichen Exceptiontypen. Oder was für Fehler – abgesehen von nicht gefundenen Dateien – können denn noch auftreten? Wahrscheinlich kannst du einiges zusammenfassen. Nur um eine Nachricht an den Benutzer zu übertragen, brauchst du doch nicht etliche verschiedene Exceptionklassen. Wenn du sie sowieso gleich auf diese reagierst (z.B. das Spiel nicht starten lässt), erst recht nicht.

    Ansonsten erklär mal, was für Fehler du abfängst und wie du auf sie reagierst.



  • Das passt jetzt vielleicht nicht mehr ganz zur aktuellen Diskussion, aber es viel mir gleich als erstes zum Thema "Catch Abfragen zusammenfassen" ein. Also...

    Eine Sache die viele C++ Programmierer nicht wissen, ist dass man zum "weiter-werfen" einer Exception mittels throw; , keinen unmittelbar umschliessenden catch Block braucht (unmittelbar im Sinn von in der gleichen Funktion, dort wo man throw; schreiben will).

    Diese praktische Eigenschaft von C++ kann man ausnützen, um z.B. sowas zu machen:

    void LogCurrentException()
    {
        try
        {
            throw; // aktuelle Exception nochmal werfen, und gleich wieder fangen
        }
        catch (std::runtime_error const& e)
        {
            // ...
        }
        catch (std::logic_error const& e)
        {
            // ...
        }
        catch (std::exception const& e)
        {
            // ...
        }
        catch (_com_error const& e)
        {
            // ...
        }
        catch (CException* e)
        {
            // ...
            e->Delete();
        }
        catch (WasAuchImmer const& e)
        {
            // ...
        }
        catch (...)
        {
            // ...
        }
    }
    
    void foo()
    {
        try
        {
            // ...
        }
        catch (...)
        {
            LogCurrentException();
        }
    }
    
    int main()
    {
        try
        {
            // ...
            return 0;
        }
        catch (...)
        {
            LogCurrentException();
            return 3;
        }
    }
    

    p.S.: Natürlich darf man LogCurrentException() nur aufrufen, wenn es eine "current exception" gibt. Was IMO kein Problem ist. Minimales mitdenken beim Programmieren darf man denke ich schon erwarten 😉



  • p.S.:
    Wenn man sich wirklich mit so grauslichen Dingen wie MFC Exceptions rumschlagen muss, wäre es vermutlich besser, das e->Delete() auszulagern:

    void LogCurrentException()
    {
        // siehe oben, nur ohne e->Delete()
    }
    
    void DeleteCurrentException()
    {
        try
        {
            throw;
        }
        catch (CException* e)
        {
            e->Delete();
        }
        // ggf. noch Code für weitere Exception Klassen deren Instanzen manuelles "cleanup" brauchen
        catch (...)
        {
            // für alles was hier landet müssen wir nichts tun
        }
    }
    
    void foo()
    {
        try
        {
            // ...
        }
        catch (...)
        {
            LogCurrentException();
            LogCurrentException(); // kann ich jetzt auch problemlos 2x aufrufen
            DeleteCurrentException();
        }
    }
    

    Und wenn man das ganze noch etwas weiter spinnen will...

    Es könnte ja mal sein, dass man auch im catch Block Dinge machen möchte, die selbst Exceptions werfen dürfen. Was auch durchaus OK ist. Zumindest was den Standard angeht - ob es Sinn macht diesen Exceptions dann wirklich zu erlauben den catch Block zu verlassen ist eine andere Frage, auf die ich hier nicht eingehen möchte.

    Langer Rede kurzer Sinn, man könnte evtl. sowas machen wollen:

    void foo()
    {
        try
        {
            // ...
        }
        catch (...)
        {
            ThisCouldThrow(); // könnte selbst eine Exception werfen, und braucht aus irgendeinem Grund noch Zugriff auf die "current exception"
            DeleteCurrentException();
        }
    }
    

    Dem aufmerksamen Beobachter wird auffallen, dass es hier zu einem leichten Problem kommen kann. Und zwar, dass, wenn ThisCouldThrow() wirklich eine Exception verursacht, unsere CException nirgends gelöscht wird.

    Um das zu umgehen, kann man sich einen Guard basteln:

    class ExceptionCleanupGuard : private boost::noncopyable
    {
    public:
        ~ExceptionCleanupGuard()
        {
            DeleteCurrentException(); // ja, selbst das geht, trotz dtor und Stack-Unwinding und allem :)
        }
    };
    

    Dann können wir unseren Code im catch Block endlich sauber schreiben, und zwar so:

    void foo()
    {
        try
        {
            // ...
        }
        catch (...)
        {
            ExceptionCleanupGuard guard; // sorgt dafür dass unsere CException gelöscht wird, selbst wenn ThisCouldThrow() selbst eine Exception wirft
            ThisCouldThrow();
        }
    }
    

    p.p.S.: C++ ist so kuuuhl 😃



  • Bisher habe ich nur Dateifehler. Aber ich will noch Fehlerklassen für ungültige Werte aus Dateien erstellen. Was sonst noch kommt, ergibt sich noch, das Projekt ist erst gestartet.

    Aber das genannte mit dem weiterwerfen ist schon die Lösung nach der ich gesucht habe. Einfach nur alle catch-Abfragen in eine Funktion packen, die nicht in main.cpp stehen muss. Vielen Dank. 🙂



  • Little Programmer schrieb:

    Aber das genannte mit dem weiterwerfen ist schon die Lösung nach der ich gesucht habe. Einfach nur alle catch-Abfragen in eine Funktion packen, die nicht in main.cpp stehen muss. Vielen Dank. 🙂

    Keine tolle Lösung.

    • Nicht jeder Fehler ist gleich eine Ausnahme. Falsche Usereingaben sind z.B. normal und sollten direkt angemessen überprüft werden, statt zu einer Ausnahme zu führen.
    • Catches gehören dahin, wo du auf die Ausnahmen auch angemessen reagieren kannst, nicht gesammelt an irgendeine Stelle.
    • Wenn du auf eine Ausnahme nicht reagieren kannst, wirf sie weiter, allerdings eventuell als etwas generellere Ausnahme, z.B. indem du "FileNotFoundException", "FileOpenError", "FileFormatError" an einer geeigneten Modulgrenze in einen allgemeinen "FileOperationError" umwandelst. (An der Stelle wo du ganz allgemein die Werte aus der Datei zurückhaben möchtests ists egal aus welchem Grund das nicht geklappt hat)


  • Little Programmer schrieb:

    Aber das genannte mit dem weiterwerfen ist schon die Lösung nach der ich gesucht habe. Einfach nur alle catch-Abfragen in eine Funktion packen, die nicht in main.cpp stehen muss. Vielen Dank. 🙂

    Schade, dass Leute in diesem Forum oft das Denken aufgeben, sobald eine mögliche Lösung gepostet wird. 😞

    Ich rate dir wirklich, Überlegungen bezüglich Design anzustellen. pumuckl hat einige Aspekte gerade nochmal schön erläutert.



  • @pumuckl

    Die von mir skizzierte Lösung ist IMO gut brauchbar, wenn man an mehreren Stellen Exceptions fangen und loggen will, und es mit vielen verschiedenen (nicht verwandten) Exception-Typen zu tun hat.
    Oder wenn man an mehreren Stellen eine Error-Message aus einer Exception extrahieren möchte, um diese dem User anzuzeigen.

    Natürlich bedeutet das nicht, dass man dann nurmehr in main() Exceptions fangen soll, habe ich auch nie geschrieben.

    Deine Argumente sind gut, aber bedeuten IMO nicht, dass ein Konstrukt der Art wie ich es hier gezeigt habe unnütz oder schlecht wäre.

    Der ganze Tanz ist ja auch nur dazu da, dass man an *mehreren* Stellen einfach Exceptions fangen und wenigstens so weit auswerten kann, dass man etwas zu loggen hat. Wenn man nur an einer Stelle fängt, dann kann man gleich dort sämtliche catch Handler hinschreiben.


  • Administrator

    pumuckl schrieb:

    • Wenn du auf eine Ausnahme nicht reagieren kannst, wirf sie weiter, allerdings eventuell als etwas generellere Ausnahme, z.B. indem du "FileNotFoundException", "FileOpenError", "FileFormatError" an einer geeigneten Modulgrenze in einen allgemeinen "FileOperationError" umwandelst. (An der Stelle wo du ganz allgemein die Werte aus der Datei zurückhaben möchtests ists egal aus welchem Grund das nicht geklappt hat)

    Was man übrigens wunderbar mit der von hustbaer vorgeschlagenen Lösung machen kann, falls dies an verschiedenen Stellen auftaucht, wodurch man Codeduplizierungen vermeiden kann 😉

    Die Lösung von hustbaer ist halt nur ein Werkzeug, wie und wo man es einsetzt ist eine andere Frage.

    Nexus schrieb:

    Schade, dass Leute in diesem Forum oft das Denken aufgeben, sobald eine mögliche Lösung gepostet wird. 😞

    Hat nichts mit diesem Forum zu tun. Ist ein sehr allgemeines Phänomen. Nennt man auch Denkfaulheit 😉

    Grüssli



  • Dravere schrieb:

    pumuckl schrieb:

    • Wenn du auf eine Ausnahme nicht reagieren kannst, wirf sie weiter, allerdings eventuell als etwas generellere Ausnahme, z.B. indem du "FileNotFoundException", "FileOpenError", "FileFormatError" an einer geeigneten Modulgrenze in einen allgemeinen "FileOperationError" umwandelst. (An der Stelle wo du ganz allgemein die Werte aus der Datei zurückhaben möchtests ists egal aus welchem Grund das nicht geklappt hat)

    Was man übrigens wunderbar mit der von hustbaer vorgeschlagenen Lösung machen kann, falls dies an verschiedenen Stellen auftaucht, wodurch man Codeduplizierungen vermeiden kann 😉

    Die Lösung von hustbaer ist halt nur ein Werkzeug, wie und wo man es einsetzt ist eine andere Frage.

    Das "keine gute Lösung" war auch nicht auf hustbaers Lösung bezogen (sorry wenn das so rübergekommen ist) sondern darauf, wie der OP diese Lösung scheinbar einsetzen will, nämlich als allround-catcher irgendwo weite genug oben im Code.



  • Auf Usereingaben und ähnliches werfe ich auch keine Exceptions. Meine Exceptions beziehen sich ausschließlich auf Fehler die das Programm nicht selbst beheben kann. Z.B. die fehlenden Dateien. In einem solchen Fall soll der User eine Meldung erhalten, ein Fehlerprotokoll wird gespeichert und das Programm soll sich beenden. Da die Prozedur immer die gleiche ist und nur die Fehlermeldung variert, ist es die beste Lösung an zentraler Stelle alle Exceptions abzufangen und in dieses Fehlerausgabesystem zu stecken. Da das Programm nichts anderes können muss, muss ich auch nicht an allen möglichen Stellen catches setzen die trotzdem nur das gleiche tun würden wie die catches, die ich als Block in der main.cpp schreibe. Falsche Usereingaben usw. erhalten eine sofortige Retour, da verwende ich nicht extra throw und catch für. Ich weiß also nicht, was ihr gegen dieses Design habt. Es ist übersichtlich und erfüllt seinen Zweck zu 100%. Aber danke für eure weiteren Ratschläge.



  • @Little Programmer:
    In dem Fall ist "meine" Lösung aber totaler Overkill.

    Dann lieber alles direkt in main() fangen.

    "Exception reflection" über "re-throw" ist IMO ein mächtiges Werkzeug.

    Allerdings sollte man die Nachteile die man sich dadurch einkauft (weniger übersichtlich, da "verstreuterer" Code, allgemein schwerer zu verstehen etc.) immer gegen die Vorteile abwägen. Da es in diesem Fall keinen für mich ersichtlichen Vorteil gibt...


Anmelden zum Antworten