it-swarm.dev

Haben wir noch einen Fall gegen die goto-Erklärung?

Mögliches Duplikat:
Lohnt es sich jemals, goto zu verwenden?

In einem neuerer Artikel schreibt Andrew Koenig:

Auf die Frage, warum goto Anweisungen schädlich sind, sagen die meisten Programmierer etwas wie "weil sie Programme schwer verständlich machen". Drücken Sie stärker, und Sie hören möglicherweise etwas wie "Ich weiß es nicht wirklich, aber das wurde mir beigebracht." Aus diesem Grund möchte ich die Argumente von Dijkstra zusammenfassen.

Er zeigt dann zwei Programmfragmente, eines ohne goto und eines mit goto:

if (n < 0)
    n = 0;

Unter der Annahme, dass n eine Variable eines eingebauten numerischen Typs ist, wissen wir, dass n nach diesem Code nicht negativ ist.

Angenommen, wir schreiben dieses Fragment neu:

if (n >= 0) goto nonneg;
n = 0;
nonneg: ;

Theoretisch sollte dieses Umschreiben den gleichen Effekt haben wie das Original. Das Umschreiben hat jedoch etwas Wichtiges geändert: Es hat die Möglichkeit eröffnet, die Kontrolle von einer anderen Stelle im Programm auf nonneg zu übertragen .

Ich habe den Teil betont, dem ich nicht zustimme. Moderne Sprachen wie C++ do not erlauben goto, die Kontrolle willkürlich zu übertragen. Hier sind zwei Beispiele:

  1. Sie können nicht zu einer Bezeichnung springen, die in einer anderen Funktion definiert ist.
  2. Sie können nicht über eine Variableninitialisierung springen.

Stellen Sie nun Ihren Code aus winzigen Funktionen zusammen, die dem Prinzip der Einzelverantwortung entsprechen:

int clamp_to_zero(int n)
{
    if (n >= 0) goto n_is_not_negative:
    n = 0;
n_is_not_negative:
    return n;
}

Das klassische Argument gegen die Anweisung goto ist, dass die Steuerung von überall in Ihrem Programm auf das Label n_is_not_negative Übertragen werden könnte, aber dies ist einfach nicht (und war) nie) wahr in C++. Wenn Sie es versuchen, wird ein Compilerfehler angezeigt, da Beschriftungen einen Gültigkeitsbereich haben. Der Rest des Programms sieht nicht einmal den Namen n_is_not_negative, Daher ist es einfach nicht möglich, dorthin zu springen. Dies ist eine statische Garantie!

Nun, ich sage nicht, dass diese Version besser ist als die ohne goto, aber um letztere so ausdrucksstark wie die erste zu machen, hätten wir es zumindest getan um einen Kommentar oder noch besser eine Behauptung einzufügen:

int clamp_to_zero(int n)
{
    if (n < 0)
        n = 0;
    // n is not negative at this point
    assert(n >= 0);
    return n;    
}

Beachten Sie, dass Sie die Zusicherung in der Version goto grundsätzlich kostenlos erhalten, da die Bedingung n >= 0 Bereits in Zeile 1 geschrieben ist und n = 0; Die Bedingung trivial erfüllt. Aber das ist nur eine zufällige Beobachtung.

Es scheint mir, dass "nicht gotos verwenden!" ist eines dieser Dogmen wie "benutze nicht mehrere returns!" das stammt aus einer Zeit, in der das Problem real Funktionen von Hunderten oder sogar Tausenden von Codezeilen waren.

Haben wir also noch einen Fall gegen die goto-Aussage, außer dass sie nicht besonders nützlich ist? Ich habe seit mindestens einem Jahrzehnt kein goto mehr geschrieben, aber es ist nicht so, als wäre ich vor Angst weggelaufen, wenn ich auf eines gestoßen bin. 1 Im Idealfall würde ich gerne ein starkes und gültig Argument gegen gotos sehen, das immer noch gilt, wenn Sie sich an etablierte Programmierprinzipien für sauberen Code wie das SRP halten. "Sie können überall springen" ist in C++ kein gültiges Argument (und war es auch nie), und irgendwie mag ich es nicht, Dinge zu unterrichten, die nicht wahr sind.

1: Außerdem konnte ich noch nie einen einzigen Velociraptor wiederbeleben, egal wie viele gotos ich ausprobiert habe :(

29
fredoverflow

Gotos sind nicht das Problem. Sie waren es nie. Das Problem sind Programmierer, die gotos so verwenden, dass es extrem schwierig ist, Code zu befolgen, und möglicherweise Code in einen unbestimmten Zustand versetzt (denken Sie daran, dass nicht jede Sprache so streng ist wie C++). Gotos sind nur die Waffe - aber der Programmierer ist derjenige, der sie auf seinen Fuß richtet und den Abzug drückt.

In der Vergangenheit habe ich gelegentlich Gotos verwendet, bei denen ich zu dem Schluss kam, dass dies die beste Vorgehensweise war (oder normalerweise die am wenigsten riskante Vorgehensweise). Daran ist nichts auszusetzen, solange Sie sich der möglichen Konsequenzen bewusst sind. Aber neue Programmierer haben nicht unbedingt diesen Sinn und können die Dinge durcheinander bringen. Ich vermute also, dass es viel einfacher ist, ihnen beizubringen, niemals Gotos zu benutzen.

38
GrandmasterB

Im Idealfall würde ich gerne ein starkes und gültiges Argument gegen gotos sehen, das immer noch gilt, wenn Sie sich an etablierte Programmierprinzipien für sauberen Code halten

goto kann anstelle eines if, eines switch, for, while, break, return und viele, viele andere Merkmale der Sprache, die alle unterschiedlichen Zwecken dienen. Es gibt jedoch einen Grund, warum Sprachen so viele Verzweigungs- und Schleifenkonstrukte erworben haben: Sie erleichtern das Erfassen der Funktionsweise des Codes. Die Verwendung von goto gibt diese wichtige Funktion auf und lässt Sie mit Assembler-ähnlichem Code entschlüsseln.

Ein goto ist daher all diesen strukturierten Sprachkonstrukten unterlegen.

Ich verfolge diese goto Debatte seit fast zwei Jahrzehnten, und in C++ habe ich noch keinen Code gesehen, der durch Anwenden eines goto verständlicher gemacht werden könnte, aber würde nicht noch einfacher zu verstehen sein, wenn RAII angewendet, zusätzliche (inline) Funktionen eingeführt oder eines der oben genannten Konstrukte verwendet wird. (Es ist anders für C, das kein RAII hat.)

26
sbi

Meiner bescheidenen Meinung nach

if (n < 0)
  n = 0;

macht vollkommen klar, dass danach n >= 0, also würde ein Kommentar, der dies erklärt, gegen das DRY -Prinzip] verstoßen, und eine Behauptung, die ich für geradezu albern oder verwirrend halte: Könnte der Autor an dieser Stelle auf einen seltsamen Compiler-/Optimierungsfehler gestoßen sein? ?

Ich halte die Variante mit goto für schwieriger zu verstehen als mit einfachem if. Dies kann natürlich einfach der Effekt sein, dass man es nicht gewohnt ist, gotos im Code zu sehen; Andererseits sind es die meisten meiner (gegenwärtigen und zukünftigen) Mitarbeiter auch nicht, also fühlen sie sich wahrscheinlich genauso. Selbst wenn ich mich an goto gewöhnen würde, würde es auf lange Sicht schwieriger sein, den Code zu pflegen.

Es scheint mir, dass "keine Gotos verwenden!" ist eines dieser Dogmen wie "Verwenden Sie nicht mehrere Renditen!" Diese stammen aus einer Zeit, in der das eigentliche Problem Funktionen von Hunderten oder sogar Tausenden von Codezeilen waren.

Alle Legacy-Programme, die ich bisher gesehen habe, enthalten viele, viele Funktionen von Hunderten (oder manchmal sogar Tausenden) Codezeilen. Daher bin ich sehr froh, dass sie zumindest keine gotos enthalten :-) Sie haben Recht, dass goto in einer kleinen, sauberen Methode kein großes Problem darstellen kann; Wenn Sie jedoch versuchen, eine unscharfe Linie wie "Sie können goto in Funktionen verwenden, die kürzer als n Linien sind" zu zeichnen, wird dies unweigerlich von "cleveren" Entwicklern missbraucht. Ganz zu schweigen davon, dass Funktionen im Laufe der Zeit tendenziell wachsen. Was tun Sie, wenn sich Ihre ursprünglich kurze und saubere Funktion aufbläht, um ihre Größe zu verdoppeln? Entfernen Sie dann die gotos oder den Refactor? Wird Ihr Nachfolger einige Jahre später die gotos entfernen oder auch den Code umgestalten? ... Die Regel von OTOH Dijkstra ist klar und hat viel weniger Potenzial, missbraucht zu werden.

Last but not least drückt der Name Ihrer Funktion nicht ihre Absicht aus. Umbenennen in z.B. make_non_negative würde keinen Zweifel darüber lassen, was es tut.

YMMV.

19
Péter Török

Es ist nichts falsch, wenn man goto richtig verwendet. Zum Beispiel:

for(int i = 0; i <= 10; ++i)
{
   for(int j = 0; j <= 10; ++j)
   {
      // ..
      if(condition)
        goto end;
      // ..
   }
}

end:

Das Problem besteht darin, goto auf eine Weise zu verwenden, die keinen Sinn ergibt, wie in Ihrem Beispiel:

if (n >= 0) goto nonneg;
n = 0;
nonneg: ;

Ich habe das noch nie in echtem Code gesehen (und ich habe viele böse Sachen gesehen), also weiß ich nicht, worum es bei all der Aufregung und dem Hass geht.

Die Verwendung von goto auf diese Weise entspricht:

for(int i = 0; n < 0 && i < 1; ++i)
    n = -5;

anstatt

if(n < 0)
    n = -5;

Dies bedeutet nicht, dass for schlecht ist, genau wie goto nicht.

14
Thomas Bonini

Ihre Beispiele sind nicht sehr überzeugend. Es gibt viele andere Fälle, in denen goto viel besser ist als jede andere Option.

Eine davon ist die Implementierung virtueller Maschinen mit der berechneten Erweiterung goto in GCC (siehe beispielsweise OCaml-Bytecode-Interpreter).

Ein weiterer (sehr weit gefasster) Fall ist die Codegenerierung. Zum Beispiel ist es viel effizienter (und auch viel einfacher zu verstehen), einen Packrat-Parser-Code aus deklarativem PEG einer höheren Ebene zu generieren, indem goto verwendet wird, um den fehlgeschlagenen Parsing-Zweigen zu entkommen. Wenn Ihre Sprache als Ziel für die Codegenerierung verwendet werden kann oder wenn es sich um eine Metasprache handelt, mit der neue Sprachfunktionen implementiert werden können, ist goto ein Muss.

Und ein dritter Fall - Zustandsautomaten. Hier ist goto eine natürliche Darstellung eines Konzepts des Wechsels von einem Zustand in einen anderen, und daher ist eine auf goto basierende Implementierung viel besser lesbar als alles andere.

Leider wurde vielen Programmierern ein fast religiöser Hass und die Angst vor dem goto beigebracht, und aus diesem Grund ist keines der oben genannten Argumente für dieses Los überzeugend genug.

8
SK-logic

Dann müssen wir die Argumente des großen Mannes berücksichtigen. Siehe eine Transkription seines Originalpapiers für Referenzen.

Meine erste Bemerkung ist, dass, obwohl die Aktivität des Programmierers endet, wenn er ein korrektes Programm erstellt hat, der Prozess, der unter der Kontrolle seines Programms stattfindet, der wahre Gegenstand seiner Aktivität ist, denn dieser Prozess muss den gewünschten Effekt bewirken. Es ist dieser Prozess, der in seinem dynamischen Verhalten die gewünschten Spezifikationen erfüllen muss. Sobald das Programm erstellt wurde, wird das „Erstellen“ des entsprechenden Prozesses an die Maschine delegiert.

Es ist also die Laufzeit, die entscheidend ist.

Meine zweite Bemerkung ist, dass unsere intellektuellen Kräfte eher darauf ausgerichtet sind, statische Beziehungen zu beherrschen, und dass unsere Kräfte zur Visualisierung von Prozessen, die sich im Laufe der Zeit entwickeln, relativ schlecht entwickelt sind. Aus diesem Grund sollten wir (als weise Programmierer, die sich unserer Grenzen bewusst sind) unser Möglichstes tun, um die konzeptionelle Lücke zwischen dem statischen Programm und dem dynamischen Prozess zu verkürzen . um die Entsprechung zwischen dem Programm (im Textraum verteilt) und dem Prozess (zeitlich verteilt) so trivial wie möglich zu gestalten.

(meine Betonung)

Unser Ziel ist es insbesondere sicherzustellen, dass unser konzeptionelles Bild des laufenden Programms so weit wie möglich mit dem tatsächlichen übereinstimmt .

Und schlussendlich

... Die ungezügelte Verwendung der go to-Anweisung hat unmittelbar zur Folge, dass es furchtbar schwierig wird, einen aussagekräftigen Satz von Koordinaten zu finden, in denen der Prozessfortschritt beschrieben werden kann. ...

Meiner Meinung nach hat sich nichts geändert, und es wird sich auch nie ändern. goto erschwert die Beschreibung des Prozessfortschritts . Und da auch bewiesen wurde, dass jeder Algorithmus, der goto verwendet, umgeschrieben werden kann, um ihn nicht zu verwenden, ruht mein Fall.

Nur weil Sie goto auf eine weniger schädliche Weise verwenden können, die nicht zu viele Regeln verletzt, tut dies nicht. ' t bedeutet, dass Sie goto verwenden sollten. Dijkstra hatte Recht und ist es immer noch. Sie müssen die Einfachheit Ihres Codes maximieren . Einfach genug ist nicht akzeptabel.

5
OldCurmudgeon

Es wurde bereits gesagt, dass Dinge wie if und while und for nur ausgefallene gotos sind (wenn auch gut strukturiert und verständlich).

Aber das Interessante ist, dass viele gotos dort erscheinen, wo wir idealerweise so etwas wie return verwenden würden. Die Antwort von @ Krelp ist ein gutes Beispiel:

... start of big function
for(int i = 0; i <= 10; ++i)
{
   for(int j = 0; j <= 10; ++j)
   {
      // ..
      if(condition)
        goto end;
      // ..
   }
}    
end:
... rest of big function

In sehr seltenen Fällen habe ich goto (vorübergehend) verwendet, wo ich gerne leichtgewichtige verschachtelte Funktionen verwendet hätte. Es wäre schön, diese for - Schleifen in eine verschachtelte Funktion einfügen und die goto durch eine return ersetzen zu können.

void big_function() {
    ... start of big function
    // next few lines declare a nested function
    void nested_function() { // shall be permitted access to the locals in the parent function
        for(int i = 0; i <= 10; ++i)
        {
           for(int j = 0; j <= 10; ++j)
           {
              // ..
              if(condition)
                return;
              // ..
           }
        }    
    }
    nested_function(); // actually call it
    ... rest of big function
}

Ich würde so etwas wirklich gerne in C und C++ sehen, vielleicht erlauben es Lambdas. In der Tat sollten globale Funktionen möglicherweise veraltet sein und alles sollte als Lambda angesehen werden! (PS: Erlauben einige C-Compiler so etwas nicht schon?)

4
Aaron McDaid

Ihr Beispiel gilt nur für triviale Funktionen, bei denen ich das Ganze auf einmal sehen kann. In längeren Funktionen ist es nicht so trivial.

Die Anweisung if garantiert keine Kontrollfreakouts im Rahmen von eine Anweisung. Ich muss nur diese eine Aussage verstehen, um zu wissen, dass nichts Lustiges passiert. Die Anweisung goto garantiert keine Kontrollfreakouts im Bereich von eine Funktion. Daher ist es inhärent, dass goto schwerer zu lesen ist als if.

0
DeadMG

Ich verwende Codierungsrichtlinien, die es goto (in Pascal: GOTO exit;) ermöglichen, bei schwerwiegenden Fehlern oder wenn aus irgendeinem Grund keine weitere Verarbeitung möglich ist, aus einer Prozedur oder Funktion auszusteigen. Um zum Ende einer Routine zu gelangen, bereinigen Sie einen Teil des Chaos (freier Speicher, Ressourcen entsperren) und beenden Sie das Programm. Wenn eine Prozedur oder Funktionsvorlage in die Quelle aufgenommen wird, um eine neue Routine zu codieren, wird die Bezeichnung 'exit' standardmäßig deklariert.

Über die Mehrfachrückgabe: Ich bin dafür, eine lokale Variable zu verwenden, um den Rückgabewert zuzuweisen. Dies kann an mehreren Standorten erfolgen. Am Ende der Funktion ist nur eine Rückgabe erforderlich. Zusammen mit goto kann dies den Code lesbarer machen, da nicht so viele verschachtelte IF-Anweisungen erforderlich sind, um das Ende der Routine zu erreichen.

Es wird jedoch immer Leute geben, die Ihnen sagen, dass Sie es nicht verwenden sollen, aber in den meisten Fällen müssen Sie den Code nicht warten.

0
Peter Hofman