Name eines Anti-Patterns gesucht



  • Morle schrieb:

    hustbaer schrieb:

    Lies vielleicht nochmal etwas weiter zurück. Darum geht's hier gar nicht.

    Es geht darum, dass if (can_do()) do() Blödsinn ist, wenn man can_do() nicht korrekt implementieren kann (bzw. es halt einfach nicht macht) ODER wenn sich die Bedingung "can do?" jederzeit ändern kann.

    Das hatten wird doch schon geklärt und waren uns auch alle einig?!.

    Hatte auch gehadert ein Follow-Up über Exceptions zu posten, aber konnte deine Aussage dann leider doch nicht so stehen lassen 😞

    Ich versteh nicht was dir nicht passt. Anhand der Formulierung deines Beitrages ist klar, dass du mir widersprichst, bzw. mich sogar belehren willst.
    Nur sachlich steht da nichts was meiner Aussage widerspricht.
    z.B. hab ich nie behauptet dass man sich keine Gedanken darüber machen sollte wann Exceptions fliegen dürfen.

    Bzw. schreibst du sogar ein paar Dinge die mMn. sogar höchst fragwürdig sind. Ich verstehe z.B. nicht warum es schlimm ist, wenn bei fehlerhaften Input-Files irgendwo ne Exception fliegt. Ich meine, dafür sind Exceptions ja schliesslich da - um Ausnahmefälle, wie fehlerhafte Input-Files, zu behandeln. Für Dinge die überhaupt niemals nicht passieren dürfen kann man Assertions verwenden (die man ja ohne weiteres auch im Release-Build mit drinlassen kann).



  • Ich meine, dafür sind Exceptions ja schliesslich da - um Ausnahmefälle, wie fehlerhafte Input-Files, zu behandeln.

    Ich dachte, Exceptions wären dafür da, Fehler, die nicht erwartet sind zu behandeln.

    Gehört denn ein fehlerhafte Datei (die vom User gegeben ist) i.A. dazu? Belehre mich, Meister!


  • Mod

    Arcoth schrieb:

    Ich meine, dafür sind Exceptions ja schliesslich da - um Ausnahmefälle, wie fehlerhafte Input-Files, zu behandeln.

    Ich dachte, Exceptions wären dafür da, Fehler, die nicht erwartet sind zu behandeln.

    Gehört denn ein fehlerhafte Datei (die vom User gegeben ist) i.A. dazu? Belehre mich, Meister!

    Natürlich ist das eine Ausnahme. Eine Datei ist eine formatierte Form der Eingabe und wenn das Format nicht passt, dann ist das eine Ausnahme.



  • Um mal den Erstsemester Talk hier zu unterbrechen und die Frage zu beantworten:
    das "Pattern", nennt sich "Easier to ask forgiveness than permission" (EAFP) bzw. "Loop before you leap" (LBYL). Keines von beiden ist ein "Anti-Pattern", beide sind halt gegensätzliche Idiome in verschiedenen Sprachen, wobei gerade für Dateisystemoperationen wie im Beispiel oder in multithreaded anwendungen EAFP viel Sinn macht, egal welche Sprache du benutzt.



  • @Arcoth
    Der Knackpunkt ist: was bedeutet "nicht erwartet"?
    Überleg dir einfach mal ein Beispiel: eine Funktion

    unique_ptr<Image> ExtractFrame(string const& videoFilePath, size_t frameNumber);

    Eine höchst unvollständige Liste von Dingen die dabei schief gehen können:
    * Das File existiert nicht
    * Das File existiert, kann aber nicht gelesen werden (z.B. weil von nem anderen Prozess mit "deny read" geöffnet)
    * Das File existiert, hat aber keines der unterstützten Container-Formate
    * Das File existiert, hat auch ein unterstütztes Container-Format, aber keinen Video-Stream
    * Das File existiert, hat auch ein unterstütztes Container-Format, aber der Video-Stream hat ein nicht unterstütztes Format
    * Das File existiert, ..., hat aber weniger Frames als frameNumber
    * Das File existiert, ..., aber irgendwo in dem ganzen Prozess ist der Speicher ausgegangen
    * Das File existiert, hat auch grundsätzliche ein gültiges Format, aber die Daten für Frame # frameNumber sind defekt (Prüfsummenfehler oder was weiss ich)
    uswusf.

    Jetzt kann man argumentieren: das sind alles Dinge die die Funktion erwarten muss, also alles "Normalfall", also nirgends Exceptions verwenden. Diese Argumentation halte ich für beknackt. Denn dann können wir Exceptions gleich einäschern -- es bleibt dann nämlich nichts mehr übrig wo wir wirklich Exceptions verwenden sollten. Ausser als Ersatz für Assertions. Nur dafür sind Exceptions eigentlich gar nicht so gut geeignet, denn wenn eine Assertion danebengeht, dann ist fraglich ob man das Programm überhaupt noch fortsetzen sollte.

    Man kann aber auch argumentieren: Es gibt hier genau einen "erwarteten" Fall, und zwar dass alles passt und die Funktion erfolgreich ein Frame extrahieren kann. Alles andere sind dann Ausnahmen, und wir verwenden dafür Exceptions. Diese Argumentation halte ich für viel sinnvoller.

    Je nachdem für was die Funktion genau eingesetzt wird können vielleicht noch weitere "normale" Fälle dazukommen. Das würde aber etwas zu weit führen, hier geht's ja nur um ein Beispiel.

    BTW: Theoretisch kann man Exceptions auch für beides verwenden, und die Fälle sogar unterscheiden. Die "Assertion-artigen" Fehler wären dann von std::logic_error abgeleitet, und alle anderen von std::runtime_error . Dummerweise gibt's aber viel zu viele Libraries die eigene Exception-Klassen verwenden, die entweder direkt von std::exception abgeleitet sind, oder gleich überhaupt gar keine Basisklasse haben. Dadurch ist es in der Praxis nicht möglich zu sagen "fang alle runtime Fehler" -- es sei denn man verwendet keine fremden C++ Libraries, und hat keinen eigenen Legacy-Code der einem dreinpfuschen würde.

    EDIT: Grammatik.



  • huhasfuhduhuhu schrieb:

    Um mal den Erstsemester Talk hier zu unterbrechen

    Oh grosser grosser Meister, belehre uns!!! 🙄

    huhasfuhduhuhu schrieb:

    "Loop before you leap" (LBYL).

    Look before you leap.
    Ich kannte den Begriff nicht, aber dass "Loop before you leap" (offenbar ein Markenname für irgendwelche Damenschuhe) Quatsch ist war irgendwie offensichtlich.



  • unique_ptr<Image> ExtractFrame(string const& videoFilePath, size_t frameNumber);
    

    Das Problem mit Funktionen im God Mode ist ... der God Mode. Ich sach nur MMMMMMMonster kill.

    Ansonsten kann das Beispiel auch sehr gut mit Fehlercodes funktionieren. Ob irgendwo ein if (...) throw ... oder if (res != SUCCESS) return ... ist dabei unerheblich. Manchmal nutze ich auch:

    bool fun1(...);
    bool fun2(...);
    
    bool b = true;
    b = b && fun1...
    b = b && fun2...
    return b;
    

    Der Unterschied ist semantischer Natur.



  • knivil schrieb:

    unique_ptr<Image> ExtractFrame(string const& videoFilePath, size_t frameNumber);
    

    Das Problem mit Funktionen im God Mode ist ... der God Mode. Ich sach nur MMMMMMMonster kill.

    Was für ein sinnfreies Kommentar!

    Dir ist schon klar dass ich in keiner Weise auch nur angedeutet habe, dass diese Funktion als Monolith implementiert sein soll, ja?
    Aber nö, ist dir vermutlich nicht klar, weil du ja immer davon ausgehst dass was andere schreiben nur Mist sein kann.

    Alter Schwede!



  • Was für ein sinnfreies Kommentar!

    Mich zu verstehen ist schwer. Der Fehler liegt bei mir.

    Monolith implementiert sein soll

    Wie sie implementiert ist, ist doch egal. Die Funktion ist ein schlechtes Beispiel, da sie zu viel macht. Deswegen halte ich das Beispiel fuer ungeeignet.

    weil du ja immer davon ausgehst dass was andere schreiben nur Mist sein kann

    Unterstellung. Aber vielleicht hast du recht: ninety percent of everything is crap



  • @knivil
    Geht's noch?

    Wenn ich genau diese Funktionalität in einem Programm brauche, dann schreib ich dafür genau so eine Funktion.
    Oder soll ich die 5-10 Zeilen die diese Funktion vermutlich haben wird dort reinschreiben, wo sonst der Funktionsaufruf stehen würde? Vielleicht auch noch an mehreren Stellen dupliziert? WTF?

    Argumentierst du jetzt für oder gegen Spaghetticode, entscheide dich!

    Davon abgesehen: dein Vorwurf "god mode" zeigt klar, dass du auf dem völlig falschen Dampfer bist. Mir ging es bei dem Beispiel gerade darum, eine Funktion zu wählen, die ganz klar sehr viele andere Klassen/Unterfunktionen benötigen wird. So dass die Tests auf die verschiedenen "Fehlerbedingungen" über viele viele Klassen/Funktionen/Schichten versteut sein werden. Weil dann auch klar wird, warum es Sinn macht dafür Exceptions zu verwenden.



  • knivil schrieb:

    Ansonsten kann das Beispiel auch sehr gut mit Fehlercodes funktionieren. Ob irgendwo ein if (...) throw ... oder if (res != SUCCESS) return ... ist dabei unerheblich. Manchmal nutze ich auch:

    bool fun1(...);
    bool fun2(...);
    
    bool b = true;
    b = b && fun1...
    b = b && fun2...
    return b;
    

    Der Unterschied ist semantischer Natur.

    Und wie mach' ich das wenn ich auch wissen will was nicht geklappt hat, nicht nur ob alles geklappt hat?



  • GetLastError, errno, Fehlercode fuer jeden Prozessschritt. Beispiel:

    enum e_video
    { e_success = 0
    , e_load_failed = 1
    , ...
    };
    
    int res = 0;
    res = load_file(...)
    if (res != e_success) return res;
    res = read_header(...)
    if (res != e_...
    

    Es sind wahrscheinlich genauso viele if s wie in einer throw Loesung. Als Spagethi-Code bezeichne ich das auch nicht. Klar, ordenlich aufgereiht, strukturiert. Einfach.

    Anmerkung: Wir sind 'Rund um die Programmierung', d.h. es geht nicht nur um Sprachen mit Exceptions. Und was fuer C gueltigkeit hat, muss noch lange nicht in C++ ungueltig sein. Von Lisp will ich gar nicht reden.



  • knivil schrieb:

    Anmerkung: Wir sind 'Rund um die Programmierung', d.h. es geht nicht nur um Sprachen mit Exceptions. Und was fuer C gueltigkeit hat, muss noch lange nicht in C++ ungueltig sein. Von Lisp will ich gar nicht reden.

    Meine Antwort mit der bösen (angeblichen) "God-Mode Funktion", bezieht sich auf eine Frage von Arcoth bezüglich Exceptions. Und jetzt darf ich in der Antwort nicht voraussetzen dass es Exceptions gibt? Spinnst du jetzt komplett?



  • Entschuldige bitte. Da liegt mein Missverstaendnis. Erwartete Fehler sind ... subjektiv. Aber um trotzdem zu antworten: Genannte Loesung funktioniert auch in Anwesenheit von Exceptions.

    Spinnst du jetzt komplett?

    Jetzt krieg dich mal wieder ein. Kann ja nicht ahnen, dass du nie UT gespielt hast.



  • @hustbaer: Vielen Dank, leuchtet ein.

    Ich lass' euch mal in Ruhe debattieren.



  • knivil schrieb:

    Es sind wahrscheinlich genauso viele if s wie in einer throw Loesung.

    Nein. Es sind viel viel mehr if's. Muss ich dafür jetzt echt ein Beispiel liefern?

    Als Spagethi-Code bezeichne ich das auch nicht. Klar, ordenlich aufgereiht, strukturiert. Einfach.

    Darauf war das auch nicht bezogen. Du kannst nicht einfach ein Wort das ich in meiner Antwort geschrieben habe hernehmen, und auf irgendwas beziehen was dir grad einfällt. Also du kannst natürlich schon, aber es macht nicht sehr viel Sinn.

    Das war bezogen auf

    knivil schrieb:

    Wie sie implementiert ist, ist doch egal. Die Funktion ist ein schlechtes Beispiel, da sie zu viel macht. Deswegen halte ich das Beispiel fuer ungeeignet.

    Aber vielleicht hab ich dich da jetzt misverstanden. Ich hab das als "die Funktion tut zu viel, ganz egal wie sie implementiert ist" verstanden. Was mMn. totaler Unfug ist.

    Falls es dir wirklich nur um die Tauglichkeit als Beispiel geht: wieso ist das ein schlechtes Beispiel? Mir geht es gerade um Dinge wo verdammt viel schief gehen kann, weil die Funktion so viel tut (und so viele andere Funktionen/Klassen/... verwendet).

    Bilderbuchbeispiele sind meist trivial. Und triviale Beispiele sind ... naja eben trivial. Und damit meistens ziemlich uninteressant.



  • Am Ende geht's sowieso nur darum was besser ist: Welcher Fehler ist als Fehlercode umzusezten, als Exception, als leeres Object, boost::optional, Haskells Either, ... Ich frage mich, warum Fehlerbehandlung/Exceptions immer so ein Streitthema sind. Wahrscheinlich sind alle Beispiel auf mehrere Arten elegant, einfach und uebersichtlich zu loesen. Deswegen sind im Falle der Fehlerbehandlung alle Beispiele ungeeignet. Unter diesem Gesichtspunkt ziehe ich meine Kritik zurueck, weil wie du bereits sagtest: trivial.

    Ok, Variante b) hat mehr ifs. Variante a) bool + errno/GetLastError oder aequivalentes nicht, bzw. short circut Anwendung.


  • Mod

    Willst du damit andeuten, eine gemischte Fehlerbehandlung wäre das Optimum? 😮
    Gerade so etwas wie errno geht ja wohl überhaupt nicht, besonders wenn man Exceptions zur Verfügung hätte.



  • knivil schrieb:

    Als Antipattern wuerde ich es nicht bezeichenen. Es gibt einige Anwendungsfaelle, beispielsweise:

    bool get_number(string_type str, double& d)
    {
        if (check_number(str)) //eg. with a regular expression or is digit or ...
           d = retrieve_number(str);
        else
          return false;
        return true;
    }
    

    Manche wuerden ja boost::lexical_cast benutzen. Ich wahrscheinlich nicht. Weils nur 'ne Zahl ist und weil ich vielleicht in C programmiere.

    Jo, klar. Da keiner an str rumfummeln kann, ist es kein "Fehler" mehr, vorher zu testen. Manchmal will man vorher testen (passt der C-Strings ins Ergebnis (strlen/if/strcpy) und manchmal nachher (gabs Übertrag bei der Addition?) und manchmal mittendrin(strncpy).

    Der Code ist doch nur ein Patternumsetzer damit Du ab jetzt statt

    if(check_number(str))
       d=retrieve_number(str);
    else
       cerr<<"fehler!";
    

    das vermeintlich bessere

    if(get_number(str,d))
       //alles ok;
    else
       cerr<<"fehler!";
    

    schreiben kannst.

    Also wenn Du obigen Code schreibst, dann bestätigst Du nicht, daß das kein Antipattern sei, sondern bestätigst, daß Du an höheren Stellen dieses Antipattern nicht verwenden willst.

    Hier mag man gerne nur eine Funktion haben statt zweien, damit die beiden gar nicht erst die Chance haben, auseinanderzulaufen, wie daß die checknumber 1e2 schluckt, die retrieve_number mindestens 1.e2 braucht. Und gerne ist die eine Funktion im "Normalfall", daß kein Fehler fliegt, auch schneller.

    Warum's bei Fehlerbehandlung Steit gibt? Hab den Eindruck, das kommt immer, wenn Du Exceptions schlechtredest.

    Und ich würde das return true dichter zum Erfolgsfalle ziehen

    bool get_number(string_type str, double& d)
    {
        if (check_number(str)) //eg. with a regular expression or is digit or ...
        {
           d = retrieve_number(str);
           return true;
        }
        else
          return false;
    }
    

    oder besser so einen Vortest nicht reinverschachteln lassen.

    bool get_number(string_type str, double& d)
    {
        if (!check_number(str)) //eg. with a regular expression or is digit or ...
           return false;
    
        d = retrieve_number(str);
        return true;
    }
    


  • knivil schrieb:

    Ok, Variante b) hat mehr ifs. Variante a) bool + errno/GetLastError oder aequivalentes nicht, bzw. short circut Anwendung.

    Ja, Variante a) kann ähnlich kompakt ausfallen (wenn man die "&&" nicht mitzählt, und Funktionen wo man z.B. Cleanup-Code benötigt).

    Wenn man keinen Returncode braucht ist das vermutlich auch OK.

    Wenn man schon einen Returncode braucht, dann kann es doof werden.
    Weil der Cleanup-Code Aufrufe enthalten könnte die den "last error" überschreiben.
    Kann man natürlich wieder "fixen" indem man den Returncode über Output-Parameter statt globale macht.
    Alles in allem bleibt's aber umständlich.


Anmelden zum Antworten