Bool und Out Parameter vs Fehler werfen.



  • Hallo zusammen,

    beim Fehlerhandling von Funktionen habe ich in unserem Quellcodes auf der Arbeit öfters gesehen, dass statt eines Fehlers geworfen wird, ein Return von bool mit einem Output Parameter von Typ String gearbeitet wird. Also so:

    bool isValid(const Auftrag &auf, std::string &error)const;
    

    Und dabei beziehe ich mich auf Situationen, wo man diese Validierung nach dem Erstellen des Objektes braucht und nicht während dem Erstellen des Objekts im Konstruktor. Ich werfe dann immer gerne ein Fehlerobjekt.

    Was ist eure Meinung dazu? Gibt es für euch Situationen, wo man dies genauso machen könnte, oder sollte man das werfen eines Fehlerobjektes bevorzugen?



  • Ähm. Es ist doch kein Fehler wenn eine isValid Funktion false zurückliefert.

    Wenn die Funktion im "nicht valid" Fall eine Exception wirft, dann sollte sie anders heissen, vielleicht assertValid oder requireValid oder sowas.

    Was besser ist hängt davon ab wie man die Funktion einsetzen möchte. Wenn man nur sicherstellen möchte dass das Objekt "valid" ist, und bei "nicht valid" Objekten dann eine Exception werfen um abzubrechen, dann ist vermutlich eine assertValid Funktion praktischer.

    Wenn man aber den "nicht valid" Fall typischerweise selbst behandeln möchte/muss, dann ist eine isValid Funktion besser.

    Und wenn beides vorkommt, dann kann man auch gerne beiden Funktionen machen.



  • @hustbaer sagte in Bool und Out Parameter vs Fehler werfen.:

    Ähm. Es ist doch kein Fehler wenn eine isValid Funktion false zurückliefert.
    Wenn die Funktion im "nicht valid" Fall eine Exception wirft, dann sollte sie anders heissen, vielleicht assertValid oder requireValid oder sowas.
    Was besser ist hängt davon ab wie man die Funktion einsetzen möchte. Wenn man nur sicherstellen möchte dass das Objekt "valid" ist, und bei "nicht valid" Objekten dann eine Exception werfen um abzubrechen, dann ist vermutlich eine assertValid Funktion praktischer.
    Wenn man aber den "nicht valid" Fall typischerweise selbst behandeln möchte/muss, dann ist eine isValid Funktion besser.
    Und wenn beides vorkommt, dann kann man auch gerne beiden Funktionen machen.

    War vielleicht ein schlechtes Beispiel. assertValid wäre in dem Fall nicht passend. Man möchte schon wissen, warum das Objekt nicht valid ist und dies dann behandeln und ggf. dem Anwender anzeigen lassen.

    Mir geht es mehr darum, ob man die Fehlerinformationen durch ein Bool, das den Fehlerstatus klar erkenntlich macht und dann über Output Parameter die Fehlerinformationen transportiert oder über ein Werfen eines Fehlers.

    Zum Beispiel bei einer SetFunktion wie dieser:

    class Customer
    {
    public:
        bool SetStreet(const std::string &street, std::string &error);
    }
    

    In diesem Fall setzt die Funktion die Straße und prüft, ob die Straße auch wirklich existiert und vielleicht noch andere Dinge. Falls nicht, wird ein false zurückgegeben und mit dem Output Parameter der Grund zurückgegeben.



  • Ich persönlich würde hier eher eine Exception werfen. Vom Anwender zu verlangen, dass er einen zusätzlichen Parameter, der nicht mal direkt übergeben werden kann, zu erzeugen, halte ich für unglaublich nervig. Gerade in der COM-Welt ist das leider ziemlich normal aber einfach unpraktisch. Dazu erzeugt es unnötige noise, die nur den eigentlichen Control-Flow verschleiert.
    Wenn ein setter eine Validierung durchführt und diese fehlschlägt, dann würde ich entsprechend einfach eine Exception werfen.

    Alternativ, wenn man keine Exceptions haben möchte, dann würde ich ein expected verwenden (std::expected wenn c++-23 zur Verfügung steht; ansonsten irgendeine der zahlreichen impls von github) und einen expected<void, std::string> als return type verwenden. Der Caller kann dann selbst entscheiden, wie relevant ein möglicher Error für ihn ist und diesen ggf einfach ignorieren.



  • Das wäre für mich ein Fall von std::expected. Da kann man Erfolg oder Fehler oder Fehlercode in den Rückgabewert packen.



  • Ich würde keine Exception werfen, Fehleingaben sind mMn ein zu erwartendes Szenario. Für pre-C++23 ohne std::expected kann man auch std::variant<T,std::string> benutzen.



  • @KK27 sagte in Bool und Out Parameter vs Fehler werfen.:

    Zum Beispiel bei einer SetFunktion wie dieser:
    class Customer
    {
    public:
    bool SetStreet(const std::string &street, std::string &error);
    }

    Bei solchen Funktionen frage ich mich immer wieder, in welcher Sprache die Fehlermeldung erscheint. Was wenn der Benutzer als Sprache Französisch haben möchte? Oder Japanisch?

    Und wie stelle ich ein gewisses Fehlerformat da sicher? Ein Beispiel:

    Fehler -123456789: Konnte keine Verbindung zum Server aufbauen.

    Der Text ist für den Kunden, der Fehlercode für den Entwickler und gibt genaue Details zum Fehler.


    BTW: Wie könnte da ein Entwickler auf einen möglichen Fehler reagieren?

    if (message == "Konnte Verbindung zum Server nicht aufbauen?")
    

    Das ist nicht schön.



  • Ich bin bei sowas auf für std::expected, Wenn das aufgrung von älterem Compiler nicht geht, würde ich wahrscheinlich ein std::pair<bool, std::string> nehmen.

    Ich finde es auch intern angenehm sprechende Fehler zu haben und nicht irgendwo erst ein Fehlercode nachgucken zu müssen. Oder auch zum loggen von Fehlern oder so.

    Internationalisierung kann man auch noch hinter schalten, wenn man denn möchte.



  • @Quiche-Lorraine sagte in Bool und Out Parameter vs Fehler werfen.:

    Bei solchen Funktionen frage ich mich immer wieder, in welcher Sprache die Fehlermeldung erscheint. Was wenn der Benutzer als Sprache Französisch haben möchte? Oder Japanisch?

    Und wie stelle ich ein gewisses Fehlerformat da sicher? Ein Beispiel:

    Fehler -123456789: Konnte keine Verbindung zum Server aufbauen.

    Wir benutzen da eine Tabelle mit Spracheinträgen und Platzhalterersetzungen, das sieht dann in etwa so aus:

    constexpr unsigned int lang_item_id = 4711;   // Eintrags-ID in der Sprachtabelle, konstant
    int const error_code = -123456789;            // Ergebnis eines Funktionsaufrufs, dynamisch
    std::string const msg = language_text( lang_item_id, error_code );
    

    Der Eintrag für diesen Text könnte dann so aussehen:
    "Fehler {%1}: Konnte keine Verbindung zum Server aufbauen"
    Je nach Sprache kann man den Platzhalter an die Stelle schieben, an die er grammatikalisch für diese Sprache gehört.



  • Ist zwar eigentlich Offtopic, aber das wir gerade irgendwie bei Localization angelangt sind: Wir nehmen dafür die Gettext API zusammen mit Poedit.



  • @DocShoe sagte in Bool und Out Parameter vs Fehler werfen.:

    Ich würde keine Exception werfen, Fehleingaben sind mMn ein zu erwartendes Szenario. Für pre-C++23 ohne std::expected kann man auch std::variant<T,std::string> benutzen.

    Wieso beschränkst du dich hier explizit auf Eingaben? Ich habe die Fragestellung eher allgemein interpretiert.
    Nichtdestotrotz erwartet diese Funktion scheinbar Werte, die feste vorgegebene Eigenschaften erfüllen müssen; ein Verstoß dagegen kann aus meiner Sicht schon durchaus als Ausnahme gewertet werden, sofern das durch das generelle Konzept ersichtlich und erforderlich ist. Möglicherweise würde auch eine einfache assertion im Debug Mode ausreichen. Kommt halt komplett darauf an, auf welcher logischen Ebene wir hier uns befinden.
    Mit expected hat man hier allerdings die wohl sogleich vielseitigste als auch für den Caller offensichtlichste Methode, um zu kommunizieren, dass hier etwas potentiell schief gehen kann. Je mehr ich darüber nachdenke, umso mehr Fan werde ich davon 🙂



  • @Schlangenmensch sagte in Bool und Out Parameter vs Fehler werfen.:

    Ist zwar eigentlich Offtopic, aber das wir gerade irgendwie bei Localization angelangt sind: Wir nehmen dafür die Gettext API zusammen mit Poedit.

    Wir nutzen einen Ansatz wie @DocShoe beschrieben hat. Ausgangspunkt ist eine Excel Tabelle, welche den Tabelleninhalt in zwei Header Dateien schreibt.


Anmelden zum Antworten