Ist goto xy wirklich so böse?



  • Ich lese immer wieder, das die Verwendung von 'goto' eine der schlimmsten Sünden ist, die ein Programmieren begehen kann. Aber in einigen Fällen bin ich immer wieder Versucht darauf zurück zu greifen. Hier mal ein Beispiel:

    //-----------------------------------------------------------------------------
    //      Create a filter of a given type
    //
    //      inp is a pointer to all available  variables of the GUI
    //
    Filter * FilterFactory::create( InData_t * inp ) {
        const char * labelstr = "???";
        Filter *    fi = 0;
        Fdata_t     fdat; // input to the concrete constructor
    ...                   // copy some data to fdat
        switch( inp->type ) {
    ...
            case  ft_IIRLP:
            case  ft_IIRHP:
                create_IIR( inp, &fdat );            // calculate the parameters
                fdat.inbuf = get_inbuf( fdat.taps ); // request the input buffer
                fi = new F_iir_df2( &fdat );         // create the filter
                if( fdat.type == ft_IIRLP )
                    labelstr = "IIR low-pass";
                else
                    labelstr = "IIR high-pass";
                break;
    ...
            default:
                break;
        }
        if( fi )
            fi->label( labelstr ); // set the label string
        return fi;
    }
    

    das ist der jetzige Zustand. Ich bin aber immer wieder Versucht so etwas zu machen:

    ...
            case  ft_IIRLP:
                    labelstr = "IIR low-pass";
    				goto ft_IIRHP_2;
            case  ft_IIRHP:
                    labelstr = "IIR high-pass";
              ft_IIRHP_2:
                create_IIR( inp, &fdat );
                fdat.inbuf = get_inbuf( fdat.taps );
                fi = new F_iir_df2( &fdat );
                break;
    ...
    

    Ist das akzeptabel und bringt das etwas, oder ist das einfach nur ein Rückfall ins BASIC Zeitalter?



  • goto ist, wie in deinem Beispiel, oft ein Versucht Redundanz zu vermeiden. Wie in deinem Beispiel gibt es dafür aber miest bessere Wege. z.B. die gemeinsamen drei Zeilen Code in eine eigene Funktion auszulagern.

    goto kann "OK" sein um Code zu überspringen. Wobei ich der Meinung bin dass es auch da quasi immer eine bessere Lösung gibt - z.B. eben Code in Hilfsfunktionen oder auch manchmal Hilfsklassen auszulagern. So richtig übel wird es aber wenn man anfängt mit goto Schleifen zu bauen - das kann sehr schnell sehr verwirrend werden.

    Und: findest du die goto Variante in deinem Beispiel wirklich besser? Ich finde die weniger klar, schwerer zu lesen.


  • Mod

    Es gibt Szenarien, in denen goto die sauberste Alternative ist. Das bestreitet im Grunde auch niemand. Wir bestreiten, dass jemand mit mangelnder Erfahrung solche Szenarien auch erkennt. Und dass die Erkenntnis, dass Sauberkeit und Länge des Codes nicht strikt korrelieren, mit Erfahrung kommt.

    case  ft_IIRLP:
            case  ft_IIRHP:
                create_IIR( inp, &fdat );            // calculate the parameters
                fdat.inbuf = get_inbuf( fdat.taps ); // request the input buffer
                fi = new F_iir_df2( &fdat );         // create the filter
                labelstr = (fdat.type == ft_IIRLP? "IIR low-pass" : "IIR high-pass");
                break;
    

    Das ist IMHO nachvollziehbarer als dein goto Code.

    Edit: Warum überhaupt ein switch ?

    if (fdat.type == ft_IIRLP || fdat.type == ft_IIRHP) {
      labelstr = (fdat.type == ft_IIRLP? "IIR low-pass" : "IIR high-pass");
      create_IIR( inp, &fdat );            // calculate the parameters 
      fdat.inbuf = get_inbuf( fdat.taps ); // request the input buffer 
      fi = new F_iir_df2( &fdat );         // create the filter
    }
    


  • Warum überhaupt zwei Abfragen (einmal if, einmal ?:)? Einfach if und else if, die drei Zeilen Code, die man dann doppelt schreiben muss, sind vermutlich weniger dramatisch als zweimal die Abfrage.



  • @ Arcoth
    Dein erster Vorschlag gefällt mir. Peinlich, das ich da nicht selber drauf gekommen bin.

    Edit: Warum überhaupt ein switch?

    Ich dachte es ist klar, das es sich hier nur um einen kurzen Ausschnitt handelt. Tatsächlich handelt es sich hier um ca. 12 Typen, die momentan auf 3 ausführende Codes für die abstrakte Klasse 'Filter' zurückgeführt werden können.



  • goto ist manchmal ganz praktisch für clean-up Code oder Fehlerbehandlung.



  • goto ist auch praktisch um verschachtelte Schleifen zu verlassen



  • goto ist auch praktisch um verschachtelte Schleifen zu verlassen

    Daran hatte ich noch gar nicht gedacht. Ich hatte mich aber immer darüber geärgert, dass ich dafür immer Zustandsflags mit schleppen musste.

    Und bei Fehlerbehandlung fällt mir noch etwas ein. Ich hatte vor einiger Zeit in eines meiner Linux Programme libpng (eine C Lib) eingebunden. Die arbeiten mit longjump für die Exception Behandlung und der Beispielcode im Tutorial ist auch voller goto's, etwa so:

    ...
    	// Initialize info structure
    	info_ptr = png_create_info_struct(png_ptr);
    	if (info_ptr == NULL) {
    		fprintf(stderr, "Could not allocate info struct\n");
    		code = 1;
    		goto finalise;
    	}
    ...
    

    In meinen eigenen C Programmen habe ich goto in den letzten 20 Jahren nur 2 mal eingesetzt und immer nur zur Fehlerbehandlung (bei C++ bisher nicht).



  • goto in C ist IMO etwas völlig anderes also goto in C++.
    Auf Grund fehlender Destruktoren in C gibt es da dauernd Situationen wo man goto gut brauchen kann - das klassische goto error/exit/cleanup halt.

    @zufallswert

    zufallswert schrieb:

    goto ist manchmal ganz praktisch für clean-up Code oder Fehlerbehandlung.

    In C++?



  • Ja. Wenn ich in bestimmten Fällen entscheide, dass es eleganter ist, zum clean-up mit goto zu verzweigen statt Exceptions zu nutzen, bedarf dies keinerlei Rechtfertigung oder gar Belehrung. Sorry ... 😃



  • zufallswert schrieb:

    Ja. Wenn ich in bestimmten Fällen entscheide, dass es eleganter ist, zum clean-up mit goto zu verzweigen statt Exceptions zu nutzen, bedarf dies keinerlei Rechtfertigung oder gar Belehrung. Sorry ... 😃

    Dazu gibt's jetzt nicht viel zu sagen. Wenn du es nicht für nötig befindest irgendwas sachliches zu antworten, dann sage ich einfach mal: nein, da liegst du völlig falsch. Sorry.


  • Mod

    einwegflasche schrieb:

    Daran hatte ich noch gar nicht gedacht. Ich hatte mich aber immer darüber geärgert, dass ich dafür immer Zustandsflags mit schleppen musste.

    Das ist tatsächlich nicht elegant lösbar. Es gibt ein Paper, an dem noch gearbeitet wird: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0082r2.pdf

    Multiple breaks
    The early termination block also provides for graceful multiple breaks. Suppose you want to iterate over a three-dimensional table and choose a particular cell. Today you might write something like this:

    vector<vector<vector<. . .>>> table = . . .;
    for (auto& x : table)
      for (auto& y : x)
        for (auto& z : y)
          if (some_condition(z))
          {
            do_something(z);
            goto DONE;
          }
    DONE:
    

    This isn’t too bad, but it gets worse if you have different exit situations. This particular problem could be solved by putting the loops in a function, then returning from the innermost loop, but not all algorithms are so easily encapsulated. With early termination blocks, you can do this:

    for (auto& x : table)
      for (auto& y : x)
        for (auto& z : y)
          if (some_condition(z))
          {
             do_something(z);
             break;
          }
          on_break break;
        on_break break;
    

    This scales well to more complicated cases since you can either continue or break on either termination condition. I would expect that the compiler could collapse the repeated breaks into a single jump, so the efficiency of the goto solution would be preserved.

    Ich vermute allerdings, dass viele solcher Fälle von verschachtelten "inner-break" for-Schleifen tatsächlich eine nicht verschachtelte, flache Iteration imitieren sollen. Im obigen Fall sollte demnach eine Schleife über eine "flat view" stattfinden. Kennt jemand ein Gegenbeispiel?



  • hustbaer schrieb:

    zufallswert schrieb:

    Ja. Wenn ich in bestimmten Fällen entscheide, dass es eleganter ist, zum clean-up mit goto zu verzweigen statt Exceptions zu nutzen, bedarf dies keinerlei Rechtfertigung oder gar Belehrung. Sorry ... 😃

    Dazu gibt's jetzt nicht viel zu sagen. Wenn du es nicht für nötig befindest irgendwas sachliches zu antworten, dann sage ich einfach mal: nein, da liegst du völlig falsch. Sorry.

    wenn du Streit suchst, hast du bei mir Pech. Ooooh 😞



  • Ich suche keinen Streit. Ich hatte gehofft dass das ganze zu einem sachlichen Argument führt. Du scheinst aber nur daran interssiert zu sein dich über andere lustig zu machen. Auch gut, kann ich nicht ändern.



  • Arcoth schrieb:

    einwegflasche schrieb:

    Daran hatte ich noch gar nicht gedacht. Ich hatte mich aber immer darüber geärgert, dass ich dafür immer Zustandsflags mit schleppen musste.

    Das ist tatsächlich nicht elegant lösbar.

    Tatsächlich ist das Problem in PHP sehr elegant gelöst. Dort kann man break einfach einen Parameter übergeben, für wieviele Schleifenschichten es gilt (ein break(2) beendet eben die Innerste und darauf Folgende). Ich weiß gerade nicht aus dem Kopf, ob das auch für continue gilt, was aber die logische Konsequenz wäre.



  • Zum Thema "double break" (bzw. "multiple break")...
    Man kann hier oft return statt break bzw. goto verwenden. Ist naheliegend und auch keine universelle Lösung, aber sollte IMO trotzdem erwähnt werden.

    Und falls nicht, ist meine Meinung dass ein goto zum Verlassen mehrerer Schleifen meist ganz klar besser ist als zusätzliche Zustandsvariablen einzuführen.

    Wobei ich finde dass hier noch eines meiner Lieblingspatterns erwähnt werden sollte, nämlich das Method-Object Pattern. Dadurch dass man damit lokale Variablen zu Membervariablen machen kann, lässt sich "return statt break" zum Abbrechen von verschachtelten Schleifen in sehr vielen Fällen anwenden, wo es mit "normalen" Methoden/Funktionen ... umständlich werden würde.



  • @ProGoTo
    Naja, also besonders schön finde ich das nicht. Dann noch lieber die Variante wo man bei break ein Label angeben kann - wobei die Einschränkung gilt dass das Label direkt auf das Ende eines "aktiven" Loops folgen muss. Ist im Prinzip das selbe, nur mMn. leichter zu lesen und weniger fehleranfällig.

    Auf continue lässt sich das ganze im Prinzip genau so anwenden, nur dass das Label dann direkt vor dem Ende eines aktiven Loops stehen muss.


  • Mod

    hustbaer schrieb:

    Wobei ich finde dass hier noch eines meiner Lieblingspatterns erwähnt werden sollte, nämlich das Method-Object Pattern. Dadurch dass man damit lokale Variablen zu Membervariablen machen kann, lässt sich "return statt break" zum Abbrechen von verschachtelten Schleifen in sehr vielen Fällen anwenden, wo es mit "normalen" Methoden/Funktionen ... umständlich werden würde.

    Könntest du das erläutern? Ich suche stets nach einer eleganten Lösung für folgendes Problem: Tief verschachtelte, hochkomplexe Rechnung (die man auch am liebsten in Unterfunktionen aufteilen würde), bei der aber unter gewissen Fällen vorzeitig klar wird, was das Ergebnis sein wird. Wie bekommt man dann schnell und sauber dieses Ergebnis heraus, ohne im Rest der Rechnung Laufzeitkompromisse einzugehen?



  • SeppJ schrieb:

    Tief verschachtelte, hochkomplexe Rechnung (die man auch am liebsten in Unterfunktionen aufteilen würde), bei der aber unter gewissen Fällen vorzeitig klar wird, was das Ergebnis sein wird. Wie bekommt man dann schnell und sauber dieses Ergebnis heraus, ohne im Rest der Rechnung Laufzeitkompromisse einzugehen?

    Wenn die Rechnung komplex und lange dauert und Ergebnisse nur selten rauskommen:

    throw ComplexCalculationResult(result);
    

    😉

    (Nicht ernst gemeint!)



  • SeppJ schrieb:

    Tief verschachtelte, hochkomplexe Rechnung (die man auch am liebsten in Unterfunktionen aufteilen würde), bei der aber unter gewissen Fällen vorzeitig klar wird, was das Ergebnis sein wird. Wie bekommt man dann schnell und sauber dieses Ergebnis heraus, ohne im Rest der Rechnung Laufzeitkompromisse einzugehen?

    ohne goto mit

    break (bei einer Schleifenebene)
    return (bei mehreren)
    

Anmelden zum Antworten