[if - else if - else] oder [switch - case - default] ?



  • Genau, toller Code!
    Es gibt da noch mehr Sachen, z.B. switch mit nichts:

    switch (value)
    {
        default:
            ;
    }
    

    oder ein switch-case ohne default-Zweig:

    switch (value)
    {
        case A:
            ...
            break;
        case B:
            ...
            break;
    }
    

    oder ein switch-case mit einem mekrwürdigen break:

    switch (value)
    {
        case A:
            ...
            ret = blabla();
            ...
            if (ret)
                break;
        case B:
        case C:
            break;
    }
    

    in case B und C steht kein Code, einfach break. Toll...



  • abc.w schrieb:

    oder ein switch-case ohne default-Zweig:

    Wenn bereits vorhin alle möglichen Fälle (in denen etwas passiert) abgedeckt sind, ist default unnötig. Du schreibst ja auch nicht zu jedem if ein else .

    abc.w schrieb:

    in case B und C steht kein Code, einfach break. Toll...

    Wo liegt das Problem? So macht man wenigstens explizit, dass es noch die Fälle B und C gibt und dass in diesen Fällen nichts getan wird. Noch ausdrücklicher ginge es nur noch mit einem Kommentar.



  • Nexus schrieb:

    abc.w schrieb:

    oder ein switch-case ohne default-Zweig:

    Wenn bereits vorhin alle möglichen Fälle (in denen etwas passiert) abgedeckt sind, ist default unnötig. Du schreibst ja auch nicht zu jedem if ein else .

    Alle möglichen Fälle abdecken - so was gibt es nicht. Ich schreibe gerne immer ein default , auch wenn es "nicht notwendig" ist, und mache noch z.B. so was:

    switch (value)
    {
        case A:
            ...
            break;
        case B:
            ...
            break;
        default:
            atomic_inc(&unexpected_case_cnt);
            break;
    }
    

    ... und manchmal schreibe ich auch ein else zu jedem if , manchmal mit einem Kommentar drin, z.B.:

    if (something)
    {
        a = blabla();
    }
    else
    {
        /* Do not touch a in this case here, because bla bla bla bla */
        a = a;
    }
    

    Nexus schrieb:

    abc.w schrieb:

    in case B und C steht kein Code, einfach break. Toll...

    Wo liegt das Problem? So macht man wenigstens explizit, dass es noch die Fälle B und C gibt und dass in diesen Fällen nichts getan wird. Noch ausdrücklicher ginge es nur noch mit einem Kommentar.

    Das Problem ist, dass für ret == 0 wird auch B und C ausgeführt. In diesem Fall steht da einfach ein break und es passiert nichts, bis jemand dort seinen Code einfügt...



  • abc.w schrieb:

    Nexus schrieb:

    abc.w schrieb:

    oder ein switch-case ohne default-Zweig:

    Wenn bereits vorhin alle möglichen Fälle (in denen etwas passiert) abgedeckt sind, ist default unnötig. Du schreibst ja auch nicht zu jedem if ein else .

    Alle möglichen Fälle abdecken - so was gibt es nicht.

    Warum nicht? Vielleicht habe ich vorher bereits sichergestellt, dass die Variable einen von mir erwarteten Wert hat. Vielleicht will ich nur eine zusätzliche Operation für ein paar Spezialfälle machen und für alles andere soll gar nichts getan werden.

    ... und manchmal schreibe ich auch ein else zu jedem if , manchmal mit einem Kommentar drin, z.B.:

    if (something)
    {
        a = blabla();
    }
    else
    {
        /* Do not touch a in this case here, because bla bla bla bla */
        a = a;
    }
    

    Finde ich nicht schön, vor allem die Selbstzuweisung. Ich würd das "else" wahrscheinlich mit in den Kommentar tun.



  • Mal unabhängig von dem, was der Compiler macht:

    Wenn es wirklich nur 2 Werte sind und sonst default-Fälle, und es einigermaßen vorhersehbar ist, dass die 2 Fälle nicht später mal um einige mehr erweitert werden, würde ich zu einem if .. else if .. else tendieren.

    Bei default im switch -Block gehe ich nach der Faustregel, dass die behandelten Fälle (meistens, wie schon gesagt, enums) durch die case abgedeckt werden und das default den Fehlerfall (also wider Erwarten und Programmierkunst ein out-of-range) behandelt.

    Meistens lässt sich das so umsetzen, und so hat man projektweit den Wiedererkennungseffekt default -> Fehlerfallbehandlung.
    Nur in wenigen Fällen kam es bisher vor, dass ich das default eben wirklich für "alle anderen Fälle" genutzt habe, weil ein if/else wegen der vielen Einzelfälle zu umständlich gewesen wäre.

    Für das Problem mit dem versehentlichen

    if( a = 1 )
    

    hab ich mir

    if( 1 == a )
    

    angewöhnt, dann meckert der Compiler, wenn man = schreibt...



  • minastaros schrieb:

    Für das Problem mit dem versehentlichen

    if( a = 1 )
    

    hab ich mir

    if( 1 == a )
    

    angewöhnt, dann meckert der Compiler, wenn man = schreibt...

    ... anstatt die Compilerwarnungen anzuschalten, was der viel bessere Weg gewesen wäre.



  • volkard schrieb:

    ... anstatt die Compilerwarnungen anzuschalten, was der viel bessere Weg gewesen wäre.

    Die sind an 😉 Ist mir nur nicht aufgefallen, dass er da warnt, weil das bei mir ja schon ein Error ist. Gut zu wissen.



  • minastaros schrieb:

    Für das Problem mit dem versehentlichen

    if( a = 1 )
    

    hab ich mir

    if( 1 == a )
    

    angewöhnt, dann meckert der Compiler, wenn man = schreibt...

    Hihi, das hört man immer wieder 🙂
    Was ich davon halte:

    • Es ist weniger übersichtlich und logisch, wenn die zu prüfende Variable am Schluss steht. Gerade wenn der Ausdruck komplizierter ist als ein Literal.
    • Man kann das nicht konsistent durchziehen. Was machst du bei a == b ?
    • Man wiegt sich in falscher Sicherheit. Zum Beispiel kann if (Function() = b) ohne Probleme kompilieren, wenn ein nicht-skalarer RValue zurückgegeben wird.
    • Dieser typische Anfängerfehler kommt so selten vor, dass es sich nicht lohnt, den Code hässlich zu machen.
    • Wie von volkard erwähnt helfen einem gewisse Compiler sogar mit einer Warnung.


  • minastaros schrieb:

    volkard schrieb:

    ... anstatt die Compilerwarnungen anzuschalten, was der viel bessere Weg gewesen wäre.

    Die sind an 😉 Ist mir nur nicht aufgefallen, dass er da warnt, weil das bei mir ja schon ein Error ist. Gut zu wissen.

    Da gibt es wohl nur drei Möglichkeiten:
    a) Warnungen nicht ignorieren (empfohlen)
    b) auch -Werror einschalten



  • minastaros schrieb:

    hab ich mir

    if( 1 == a )
    

    angewöhnt, dann meckert der Compiler, wenn man = schreibt...

    Wenn ich sowas lese verfalle ich in Blutrausch.
    Yoda-Programmieren das ist, Reihenfolge in dieser normaler Mensch denkt keiner.
    Also warum verdammt sollte man es dann in der Reihenfolge hinschreiben?
    *rage*



  • Besten Dank. Auch wenn wir bereits OT diskutieren, noch kurz die Antwort:

    @Nexus: Ich seh es eher als Flüchtigkeitsfehler, kann aus Unachtsamkeit immer mal wieder passieren (wenn auch extrem selten).
    Alle Fälle erschlägt man mit dem "Trick" nicht, man minimiert zumindest die Fälle mit konstanten Werten.

    a == b
    

    : Genau da würde die Compilerwarnung ja auch jetzt schon greifen. Ist mir halt noch nicht vorgekommen.

    @Volkard: Ich sehe Warnungen immer als Fehler an: Keine Freigabe mit Warnungen. Aber zum Experimentieren kann man sie manchmal tolerieren, d.h. kein Werror.
    Die Sache ist einfach so: Das hab ich mir das mal irgendwann angewöhnt, und seit dem ist es so drin. Als eine Lösung für die Bedenken von klöklö weiter vorn im Thread würde es zumindest reichen.

    Insgesamt und spätestens durch Argument "Hässlichkeit" sehe ich die Sache mal wieder als überdenkenswert an.

    @hustbaer:

    Blutrausch ... kein normaler Mensch

    Hey, locker bleiben, das steht in durchaus guten Büchern als präventive Maßnahme. Es funktionert und ist besser als gar nichts, aber wie gesagt, ich sehe es absolut ein, dass man durch Warnungen (und dann die "richtige" Reihenfolge der Operanden) wesentlich eleganter zum Ziel kommt, und das heißt: sicherer Code.
    *tröst*



  • Lesbarkeit zugunsten der Verminderung von Fehleranfälligkeit derjenigen schlechter zu stellen, die noch nicht geübt genug sind Flüchtigkeitsfehler dieser Art nicht mehr zu machen, lässt den Code automatisch als Anfängercode charakterisieren, auch Mal daran gedacht?

    Und wenn man sich das angewöhnt, muss man es sich auch erstmal wieder abgewöhnen, die Mühe sollte man sich sparen.

    Wenn man geübter ist, sieht man = statt == sofort, auch binnen Sekunden bei einem 50-100-Zeilen-Quelltext. Also besser daran arbeiten, dass man auf diesen Stand kommt. Und wenn man sich vertut, hilft einem Mr. Compiler weiter. 🙂



  • minastaros schrieb:

    Hey, locker bleiben, das steht in durchaus guten Büchern als präventive Maßnahme.

    Die guten C++-Bücher sind alle so modern, daß sie von ordentlichen Compilern ausgehen.



  • Eisflamme schrieb:

    Wenn man geübter ist, sieht man = statt == sofort,...

    "Defensives Programmieren" geht ja gerade davon aus, dass man auch als Erfahrener eben trotzdem mal was übersehen kann, und sei es ein einziges Mal unter tausenden, und Maßnahmen einzuführen, die das von vorne herein verhindern. Jeder lernt unterschiedlich, man entwickelt sich weiter, und ich z.B. habe meine "Erfahrung" durch den Compilerhinweis heute erweitert.
    Übrigens fand ich bislang die Lesbarkeit eines "1 == a" weit weniger störend und den Nutzen größer als z.B. dasZusammenschreibenvonmathematischenAusdrücken, was man so oft sieht.

    volkard schrieb:

    Die guten C++-Bücher sind alle so modern, daß sie von ordentlichen Compilern ausgehen

    Das, was ich konkret meine ("Code complete"), sieht Defensives Programmieren zunächst mal sprachunabhängig und diskutiert - in mehreren Programmiersprachen und oder sogar nur Pseudocode - einen ganzen Haufen möglicher Maßnahmen. Vom Compiler ist auf dieser Ebene noch gar nicht die Rede (wie gesagt, ich stimme dem ansonsten ja voll zu).

    Ich denke, wir sollten nicht weiter über ein = streiten.



  • minastaros schrieb:

    Eisflamme schrieb:

    Wenn man geübter ist, sieht man = statt == sofort,...

    "Defensives Programmieren" geht ja gerade davon aus, dass man auch als Erfahrener eben trotzdem mal was übersehen kann, und sei es ein einziges Mal unter tausenden, und Maßnahmen einzuführen, die das von vorne herein verhindern. Jeder lernt unterschiedlich, man entwickelt sich weiter, und ich z.B. habe meine "Erfahrung" durch den Compilerhinweis heute erweitert.
    Übrigens fand ich bislang die Lesbarkeit eines "1 == a" weit weniger störend und den Nutzen größer als z.B. dasZusammenschreibenvonmathematischenAusdrücken, was man so oft sieht.

    Du musst den Nutzen von 1 == a mit der verschlechterten Lesbarkeit vergleichen, ich finde das rechnet sich nicht.

    Und ich weiß nicht, was Du mit dem Zusammenschreiben von mathematischen Ausdrücken meinst, Du schreibst ja gerade einen Namen auf? Und falls Du lange aber sprechende Funktionsnamen meinst, kann ich entgegnen, dass:

    1. , wenn die Länge nötig ist, um es sprechend zu schreiben, es für das Verständnis des Codes durch sprechende Namen auch wert ist
    2. Du es nicht richtig gemacht hast, weil von und mathematischen auch groß geschrieben sein müsste 🤡


  • minastaros schrieb:

    "Defensives Programmieren" geht ja gerade davon aus, dass man auch als Erfahrener eben trotzdem mal was übersehen kann, und sei es ein einziges Mal unter tausenden, und Maßnahmen einzuführen, die das von vorne herein verhindern.

    So kommen wir nicht weiter. Dann müsste man z.B. auch den Code mit irrelevanten const -Schlüsselwörtern fluten, um zwar die Fehleranfälligkeit einzuschränken, mit ihr aber gleich die Benutzbarkeit, Übersichtlichkeit und Flexibilität. Man dürfte keine namespace detail mehr verwenden, weil man ja aus Versehen darauf zugreifen könnte. Immer NVI einsetzen, damit man unabsichtlich qualifizierte Aufrufe verhindert. Selbst bei internen Klassen immer alles private machen und mit etlichen friend s oder Zugriffsfunktionen verzieren. Klassen müsste man immer gleich mit virtuellen Destruktoren ausstatten für den Fall, dass sie eines fernen Tages polymorph und mit new benutzt werden.

    Fakt ist, dass du in C++ vieles nicht 100% sicher hinbringst. Die Versuche, es dennoch zu tun, führen selten zum Ziel, aber bringen ungerechtfertigt viele Nachteile und Schein-Sicherheiten mit sich. Das ist ein zu hoher Preis, um einen nie auftretenden Fehler teilweise zu verhindern.



  • Nexus schrieb:

    Dann müsste man z.B. auch den Code mit irrelevanten const -Schlüsselwörtern fluten, um zwar die Fehleranfälligkeit einzuschränken, mit ihr aber gleich die Benutzbarkeit, Übersichtlichkeit und Flexibilität.

    Definiere irrelevant. Ich schreib so oft const , ich könnte da ne eigene Taste dafür brauchen. Finde ich auch gut so.

    Man dürfte keine namespace detail mehr verwenden, weil man ja aus Versehen darauf zugreifen könnte.

    Ne, aus versehen greift man nicht auf Detail-Namespaces zu. Also gut, es kann Unfälle mit ADL geben. Aber auch nur wenn man z.B. Basisklassen für "nicht-detail" Typen in den Detail-Namespace steckt. Wo sie IMO nix verloren haben, sobald sie zum Interface von "nicht-detail" Typen gehören.

    Immer NVI einsetzen, damit man unabsichtlich qualifizierte Aufrufe verhindert.

    Ja, NVI kann lästig werden. Führt zu deutlich mehr Code als wenn man einfach nach dem Motto "muss man halt wissen was man tut" programmiert. Hat aber auch Vorteile.

    Selbst bei internen Klassen immer alles private machen und mit etlichen friend s oder Zugriffsfunktionen verzieren.

    Mach ich bei fast allen internen Klassen. Also alle die mehr sind als eine Sammlung von Variablen + Konstruktor. Sobald etwas Memberfunktionen hat wird es zugemacht.

    Klassen müsste man immer gleich mit virtuellen Destruktoren ausstatten für den Fall, dass sie eines fernen Tages polymorph und mit new benutzt werden.

    Er. Klassen immer gleich mit virtuellem Dtor machen wäre IMO sogar kontraproduktiv. Ein virtueller Dtor impliziert für mich nämlich die Aussage "diese Klasse ist als Basisklasse ausgelegt" bzw. "es ist OK und kann Sinn machen von dieser Klasse abzuleiten". Was oft falsch ist. Und Code der falsche Dinge behauptet (wenn auch nur implizit), verhindert sicher keine Fehler.

    Fakt ist, dass du in C++ vieles nicht 100% sicher hinbringst. Die Versuche, es dennoch zu tun, führen selten zum Ziel, aber bringen ungerechtfertigt viele Nachteile und Schein-Sicherheiten mit sich.

    Äh.
    Was sind denn die Nachteile an const-korrektem Code oder Kapselung von internen Klassen?
    Ausser dass man dadurch minimal länger braucht den Code ursprünglich zu schreiben fällt mir jetzt kein Grund ein.

    Die Wartbarkeit wird dadurch aber gesteigert, da es ja nicht nur ein paar unabsichtliche Fehler verhindert, sondern auch das Verstehen des Code vereinfacht. Das rechnet sich sehr schnell.

    const sagt mir sofort was wenn ich es sehe, nämlich dass hier etwas nur gelesen wird, bzw. nur zum Lesen weitergereicht/entgegengenommen etc.

    Kapselung von internen Klassen ist auch gut, da es den Code in kleinere Stücke unterteilt. Wenn ich Code lesen muss, und da eine "offene" interne Klasse sehe die drei Memberfunktionen hat, dann sagt mir das nicht viel. Es könnte 1000 Stellen geben wo die Member modifiziert werden. Vielleicht war der Author des Codes so nett und hat das nicht gemacht, aber ich sehe das nicht auf den 1. Blick.
    Wenn da allerdings "private:" steht, dann weiss ich dass ich hier eine schön gekapselte Klasse betrachte, und die 3 Memberfunktionen alles sind was ich mir angucken muss, um diesen Teil zu verstehen. Der Code zerfällt in kleinere Stücke, und wird dadurch schneller verständlich.

    Ich sehe hier auch einen wesentlichen Unterschied zu "if (1 == var)": Die "if (1 == var)" Schreibweise sagt mir nichts was mir "if (var == 1)" nicht auch sagt.

    const-korrekter Code und Kapselung erzählen mir dagegen etwas, und zwar über andere Stellen des Programms. Das macht IMO vieles einfacher.
    Ich musste schon oft genug Code lesen und/oder bearbeiten wo diese Dinge (const-Korrekter Code etc.) nicht gemacht wurden, und es war jedes mal grauenhaft. Code der diese und andere Dinge macht um "lesbar" zu sein finde ich dagegen viel viel angenehmer zu lesen. Auch wenn es dadurch ein paar Zeilen mehr werden.

    BTW: Weil die Bezeichnung "defensiv programmieren" in diesem Thread genannt wurde: ich würde das auch nicht als "defensiv programmieren" bezeichnen, sondern einfach als strukturiert programmieren.



  • Ich weiss nicht, ob du meinen Beitrag richtig verstanden hast. Natürlich sind die genannten Sicherheitsmechanismen gut, aber ihr übermässiger Einsatz ist es nicht. Du solltest mich doch genug gut kennen, um zu wissen, dass ich const , private etc. nicht generell meide 😉

    Zum virtuellen Destruktor bin ich genau deiner Meinung, ich hab mich hier sogar schon einige Male gegen automatisch virtuelle Destruktoren bei Vererbung ausgedrückt (weil halt Vererbung nicht immer Polymorphie mit sich bringt). Und mich dabei ebenfalls darauf bezogen, dass man mit dem Vorhandensein von virtuellen Destruktoren vieles über die Semantik der Klasse aussagt.

    Bei zu viel const ist das Problem einerseits in der Schnittstelle (folgender Code), andererseits kann man durch unüberlegten Einsatz die Flexibilität von Objekten stark einschränken. Ein const -Member nimmt der Klasse z.B. ihre Wertsemantik und verunmöglicht ein Speichern in STL-Containern, zumindest in C++98. Das hat auch nichts mehr mit Const-Correctness zu tun.

    void Car::SetSpeed(const float speed);
    const float Car::GetSpeed() const;
    

    Kapselung genau gleich. Natürlich verwende ich sie auch intern. Aber wenn etwas nur Daten bündelt oder sehr lokal ist (z.B. Funktor), verzichte ich darauf, selbst wenn man dadurch potenziell Fehler einbauen könnte.



  • Ich weiss nicht ob du deinen Beitrag verständlich geschrieben hast 🤡

    Ja, was top-level const bei Parametern angeht bin ich ganz deiner Meinung.

    Was const bei Membern angeht: die meisten meiner Klassen sind keine Werte-Klassen, und daher explizit noncopyable (über privates Ableiten von boost::noncopyable).
    In denen mach' ich dann auch "const as const can" bei Membern. z.T. schreibe ich eigene Hilfsfunktionen zum Initialisieren, nur damit ich ein paar Member mehr const machen kann 🙂

    Manche Leute überlegen sich bei C++ auch bei jeder Klasse wie sie diese kopierbar und zuweisbar machen können, weil das ja der "C++ way of doing it" sei. Das finde ich nicht gut. Manche überspringen den Schritt auch sich was zu überlegen, und schreiben Klassen die zwar zuweisbar sind (weil zufällig nichts vorkommt was das Generieren des Assignment-Operators verhindert), wo es dann aber knallt wenn man es macht. Das finde ich noch schlechter 🤡

    Ich mache keins von beiden. Wenn etwas für mich nicht sofort nach "reine Value-Semantik" riecht, dann mache ich es erstmal noncopyable (und das bedeutet meist auch "partiell-immutable"). Wenn sich im weiteren Verlauf herausstellt dass das doof war, dann wird halt refactored. Kein Beinbruch.

    Auch gibt es Fälle wo etwas zwar vom rein logischen her Value-Semantik hat (haben sollte), aber es
    * aus bestimmten Gründen (*) schlecht wäre es so zu implementieren und
    * es auch nicht schadet wenn man die Klasse ganz oder teilweise immutable macht, da die Funktionalität einfach nirgends gebraucht wird.

    In den Fällen mache ich die Dinger dann auch "immutable". Und dann bekommen die Member auch alle const (sofern es mit vertretbarem Aufwand möglich ist).

    (*) Gründe dafür die mir häufiger begegnen:

    1. es ist einfach (zu) viel Aufwand korrekte Zuweisungssemantik zu implementieren
    2. es verhindert (zu) viele Optimierungen in der Klasse selbst
    3. es verhindert (zu) viele Optimierungen in anderen Klassen

    DAS Beispiel aus der SCL das mir bei dem Thema immer einfällt ist std::string . IMO wäre einiges einfacher, und auch einfacher performant zu implementieren, wenn es da die von Java bekannte Teilung String/StringBuilder gäbe (wobei der String dann immutable ist).


Anmelden zum Antworten