Rechtfertigt dies goto-Aussagen?

15

Ich bin vor einer Sekunde auf diese Frage gestoßen und ziehe einen Teil des Materials von dort ab: Gibt es einen Namen für das 'break n'-Konstrukt?

Dies scheint eine unnötig komplexe Möglichkeit zu sein, das Programm anweisen zu müssen, aus einer doppelt verschachtelten for-Schleife auszubrechen:

for (i = 0; i < 10; i++) {
    bool broken = false;
    for (j = 10; j > 0; j--) {
        if (j == i) {
            broken = true;
            break;
        }
    }
    if (broken)
        break;
}

Ich weiß, dass Lehrbücher gerne sagen, dass goto-Aussagen der Teufel sind, und ich selbst mag sie überhaupt nicht, aber rechtfertigt dies eine Ausnahme von der Regel?

Ich suche nach Antworten, die sich mit n-geschachtelten for-Schleifen befassen.

HINWEIS: Unabhängig davon, ob Sie mit Ja, Nein oder irgendwo dazwischen antworten, sind völlig aufgeschlossene Antworten nicht erwünscht. Vor allem, wenn die Antwort Nein lautet, geben Sie einen guten, legitimen Grund dafür an (der ohnehin nicht zu weit von den Stack Exchange-Bestimmungen entfernt ist).

Panzerkrise
quelle
2
Wenn Sie die innere Schleifenbedingung so schreiben können, for (j = 10; j > 0 && !broken; j--)brauchen Sie die break;Anweisung nicht. Wenn Sie die Deklaration von brokennach verschieben, bevor die äußere Schleife beginnt, dann, wenn das geschrieben werden könnte, for (i = 0; i < 10 && !broken; i++)dann denke ich, dass keine break; s notwendig sind.
FrustratedWithFormsDesigner
8
Das Beispiel stammt aus C # - der C # -Referenz auf goto: goto (C # -Referenz) - "Die goto-Anweisung ist auch nützlich, um tief verschachtelte Schleifen zu verlassen."
OMG, ich wusste nie, dass c # eine goto-Aussage hat! Die Dinge, die Sie auf StackExchange lernen. (Ich kann mich nicht erinnern, wann ich mir das letzte Mal eine goto-Statement-Funktion gewünscht habe, sie scheint nie aufgetaucht zu sein.)
John Wu
6
IMHO hängt es wirklich von Ihrer "Programmierreligion" und der aktuellen Mondphase ab. Für mich (und die meisten Leute um mich herum) ist es in Ordnung, ein goto zu verwenden, um aus tiefen Nestern herauszukommen, wenn die Schleife eher klein ist, und die Alternative wäre eine überflüssige Überprüfung einer bool-Variablen, die nur eingeführt wird, um die Schleifenbedingung zu unterbrechen. Dies gilt umso mehr, wenn Sie in der Mitte des Körpers ausbrechen möchten oder wenn nach der innersten Schleife ein Code vorhanden ist, der auch den Bool überprüfen müsste.
PlasmaHH
1
@JohnWu gotomuss in einem C # -Schalter tatsächlich in einen anderen Fall übergehen, nachdem ein Code im ersten Fall ausgeführt wurde. Dies ist der einzige Fall, den ich bisher verwendet habe.
Dan Lyons

Antworten:

33

Die offensichtliche Notwendigkeit einer Go-to-Anweisung entsteht, wenn Sie schlechte Bedingungsausdrücke für die Schleifen auswählen .

Sie geben an, dass die äußere Schleife so lange andauern soll i < 10und die innerste so lange andauern soll j > 0.

Aber in Wirklichkeit ist es nicht das, was Sie wollten . Sie haben den Loops einfach nicht den tatsächlichen Zustand mitgeteilt, den sie auswerten sollen, und Sie möchten ihn lösen, indem Sie break oder goto verwenden.

Wenn Sie den Loops von Anfang an Ihre wahren Absichten mitteilen , müssen Sie weder pausieren noch loslegen.

bool alsoThis = true;
for (i = 0; i < 10 && alsoThis; i++)
{    
    for (j = 10; j > 0 && alsoThis; j--)
    {
        if (j == i)
        {
            alsoThis = false;
        }
    }
}
Tulains Córdova
quelle
Siehe meine Kommentare zu den anderen Antworten. Der Originalcode wird iam Ende der äußeren Schleife gedruckt. Das Kurzschließen des Inkrements ist daher wichtig, damit der Code zu 100% äquivalent aussieht. Ich sage nicht, dass irgendetwas in Ihrer Antwort falsch ist, ich wollte nur darauf hinweisen, dass das sofortige Unterbrechen der Schleife ein wichtiger Aspekt des Codes war.
pswg
1
@pswg Ich sehe iam Ende der äußeren Schleife in der Frage von OP keinen Codedruck .
Tulains Córdova
OP hat den Code aus meiner Frage hier referenziert , diesen Teil jedoch weggelassen. Ich habe dich nicht abgelehnt, übrigens. Die Antwort ist in Bezug auf die richtige Auswahl der Schleifenbedingungen völlig richtig.
pswg
3
@pswg Ich denke, wenn Sie wirklich wirklich wirklich wirklich einen goto rechtfertigen wollen, können Sie ein Problem erkennen, das eines benötigt. Andererseits programmiere ich seit zwei Jahrzehnten professionell und habe kein einziges reales Problem, das es braucht einer.
Tulains Córdova
1
Der Zweck des Codes war nicht, einen gültigen Anwendungsfall für goto(ich bin sicher, es gibt bessere) bereitzustellen , auch wenn er eine Frage break(n)aufwirft, die ihn als solche zu verwenden scheint, sondern um die grundlegende Aktion in Sprachen zu veranschaulichen, die dies nicht taten. nicht unterstützen.
pswg
34

In diesem Fall könnten Sie den Code in eine separate Routine umgestalten und dann direkt returnaus der inneren Schleife. Das würde die Notwendigkeit, aus der äußeren Schleife auszubrechen, zunichte machen:

bool isBroken() {
    for (i = 0; i < 10; i++)
        for (j = 10; j > 0; j--)
            if (j == i) return true;

    return false;
}
Roger
quelle
2
Das heißt, ja, was Sie zeigen, ist ein unnötig komplexer Weg, um von verschachtelten Schleifen abzubrechen, aber nein, es gibt einen Weg, um eine Refaktorierung durchzuführen, der ein Goto nicht attraktiver macht.
Roger
2
Nur eine Randnotiz: Im Originalcode wird inach dem Ende der äußeren Schleife gedruckt . Um das wirklich zu reflektieren, iist das der wichtige Wert, hier sollte das return true;und return false;beides sein return i;.
pswg
+1, wollte gerade diese Antwort posten und glaube, dies sollte die akzeptierte Antwort sein. Ich kann nicht glauben, dass die akzeptierte Antwort akzeptiert wurde, da diese Methode nur für eine bestimmte Teilmenge von Nested-Loop-Algorithmen funktioniert und den Code nicht unbedingt wesentlich sauberer macht. Die Methode in dieser Antwort funktioniert im Allgemeinen.
Ben Lee
6

Nein, ich denke nicht, dass eine gotonotwendig ist. Wenn du es so schreibst:

bool broken = false;
saved_i = 0;
for (i = 0; i < 10 && !broken; i++)
{
    broken = false;
    for (j = 10; j > 0 && !broken; j--)
    {
        if (j == i)
        {
            broken = true;
            saved_i = i;
        }
    }
}

Wenn Sie &&!brokenbeide Loops haben, können Sie aus beiden Loops ausbrechen, wenn i==j.

Für mich ist dieser Ansatz vorzuziehen. Die Loop - Bedingungen sind sehr klar: i<10 && !broken.

Eine funktionierende Version finden Sie hier: http://rextester.com/KFMH48414

Code:

public static void main(String args[])
{
    int i=0;
    int j=0;
    boolean broken = false;
    for (i = 0; i < 10 && !broken; i++)
    {
        broken = false;
        for (j = 10; j > 0 && !broken; j--)
        {
            if (j == i)
            {
                broken = true;
                System.out.println("loop breaks when i==j=="+i);
            }
        }
    }
    System.out.println(i);
    System.out.println(j);
}

Ausgabe:

Schleife bricht ab, wenn i == j == 1
2
0
FrustratedWithFormsDesigner
quelle
Warum verwenden Sie auch brokenin Ihrem ersten Beispiel überhaupt ? Benutze einfach break auf der inneren Schlaufe. Auch, wenn Sie unbedingt verwenden müssen broken, warum deklarieren Sie es außerhalb der Schleifen? Erkläre es einfach in der iSchleife. Was die whileSchleife anbelangt, verwenden Sie diese bitte nicht. Ich denke ehrlich, es schafft es, noch weniger lesbar zu sein als die goto-Version.
Luiscubal
@ luiscubal: Eine Variable mit dem Namen brokenwird verwendet, weil ich die breakAnweisung nicht gerne benutze (außer bei switch-case, aber das war's auch schon). Es wird außerhalb der äußeren Schleife deklariert, falls Sie ALLE Schleifen unterbrechen möchten, wenn i==j. Sie haben Recht, im Allgemeinen muss brokennur vor der äußersten Schleife deklariert werden, dass sie unterbrochen wird.
FrustratedWithFormsDesigner
In Ihrem Update i==2am Ende der Schleife. Die Erfolgsbedingung sollte auch das Inkrement kurzschließen, wenn es zu 100% a äquivalent sein soll break 2.
pswg
2
Ich persönlich bevorzuge broken = (j == i)es, wenn und wenn die Sprache es zulässt, die gebrochene Deklaration in die Initialisierung der äußeren Schleife einzufügen. Obwohl es schwierig ist, diese Dinge für dieses spezielle Beispiel zu sagen, da die gesamte Schleife ein No-Op ist (sie gibt nichts zurück und hat keine Nebenwirkungen) und wenn sie etwas tut, tut sie dies möglicherweise innerhalb des If (obwohl ich es immer noch broken = (j ==i); if broken { something(); }besser finde) persönlich)
Martijn
@Martijn In meinem Originalcode wird inach dem Ende der äußeren Schleife gedruckt . Mit anderen Worten, der einzige wirklich wichtige Gedanke ist, wann genau er aus der äußeren Schleife ausbricht.
pswg
3

Die kurze Antwort lautet "es kommt darauf an". Ich befürworte, hinsichtlich der Lesbarkeit und Verständlichkeit Ihres Codes zu wählen. Der Weg dorthin ist möglicherweise besser lesbar, als die Bedingung zu ändern, oder umgekehrt. Sie können auch das Prinzip der geringsten Überraschung, die im Shop / Projekt verwendete Richtlinie und die Konsistenz der Codebasis berücksichtigen. Zum Beispiel ist es in der Linux-Kernel-Codebasis üblich, den Schalter zu verlassen oder Fehler zu behandeln. Beachten Sie auch, dass einige Programmierer möglicherweise Probleme haben, Ihren Code zu lesen (entweder mit dem Mantra "goto is evil" oder einfach nicht gelernt, weil ihr Lehrer dies denkt), und einige von ihnen könnten Sie sogar "beurteilen".

Im Allgemeinen ist es oft akzeptabel, in derselben Funktion weiterzugehen, insbesondere wenn der Sprung nicht zu lang ist. Ihr Anwendungsfall, eine verschachtelte Schleife zu verlassen, ist eines der bekannten Beispiele für "nicht so böse".

FabienAndre
quelle
2

In Ihrem Fall ist goto eine Verbesserung genug, die verlockend wirkt, aber es ist nicht so sehr eine Verbesserung, dass es sich lohnt, die Regel gegen die Verwendung in Ihrer Codebasis zu brechen.

Denken Sie an Ihren zukünftigen Rezensenten: Er wird denken müssen: "Ist diese Person ein schlechter Kodierer oder ist dies tatsächlich ein Fall, in dem sich das Goto gelohnt hat? Lassen Sie mich 5 Minuten dauern, um dies für mich selbst zu entscheiden oder es auf der Website zu recherchieren Internet." Warum um alles in der Welt würden Sie das Ihrem zukünftigen Programmierer antun, wenn Sie goto nicht vollständig verwenden können?

Im Allgemeinen gilt diese Mentalität für jeden "schlechten" Code und definiert sie sogar: Wenn er jetzt funktioniert und in diesem Fall gut ist, aber Anstrengungen unternimmt, um zu überprüfen, ob er korrekt ist, was in dieser Ära ein Goto immer tun wird, dann nicht Tu es.

Davon abgesehen, ja, Sie haben einen der etwas dornigeren Fälle vorgelegt. Du darfst:

  • Verwenden Sie komplexere Kontrollstrukturen wie eine Boolesche Flagge, um aus beiden Schleifen auszubrechen
  • Stellen Sie Ihre innere Schleife in ihre eigene Routine (wahrscheinlich ideal)
  • Setze beide Schleifen in eine Routine und kehre zurück, anstatt zu brechen

Es ist sehr schwer für mich, einen Fall zu schaffen, in dem ein Double-Loop nicht so zusammenhängend ist, dass es sowieso nicht seine eigene Routine sein sollte.

Die beste Diskussion, die ich je gesehen habe, ist übrigens Steve McConnells Code Complete . Wenn Sie gerne kritisch über diese Probleme nachdenken, empfehle ich nachdrücklich, sie zu lesen, auch wenn sie so unermesslich ist.

djechlin
quelle
1
Es ist unsere Aufgabe, Code zu lesen und zu verstehen. Nicht nur vereinfachter Code, es sei denn, das ist die Natur Ihrer Codebasis. Es gibt und wird immer Ausnahmen von der Allgemeinheit (nicht der Regel) gegen die Verwendung von gotos geben, da die Kosten verstanden werden. Wann immer der Nutzen die Kosten überwiegt, ist, wann goto verwendet werden sollte; wie bei allen Kodierungsentscheidungen im Software Engineering.
Dietbuddha
1
@dietbuddha Verstöße gegen akzeptierte Software-Kodierungskonventionen verursachen Kosten, die angemessen berücksichtigt werden sollten. Es ist mit Kosten verbunden, die Vielfalt der in einem Programm verwendeten Steuerstrukturen zu erhöhen, selbst wenn diese Steuerstruktur für eine Situation gut geeignet sein kann. Die statische Analyse von Code ist mit Kosten verbunden. Wenn sich das alles noch lohnt, über wen soll ich streiten?
Djechlin
1

TLD; DR: Nein im Einzelnen, ja im Allgemeinen

Für das konkrete Beispiel, das Sie verwendet haben, würde ich aus den gleichen Gründen, auf die viele andere hingewiesen haben, Nein sagen. Sie können den Code auf einfache Weise in eine gleichwertige Form umgestalten, bei der das nicht mehr erforderlich ist goto.

Im Allgemeinen ist das Verbot dagegen gotoeine Allgemeinheit, die auf dem Verständnis gotound den Kosten ihrer Verwendung beruht . Ein Teil des Software-Engineerings besteht darin, Implementierungsentscheidungen zu treffen. Die Rechtfertigung dieser Entscheidungen erfolgt durch Abwägen der Kosten gegen den Nutzen und immer dann, wenn der Nutzen die Kosten überwiegt, ist die Umsetzung gerechtfertigt. Kontroversen ergeben sich aus unterschiedlichen Bewertungen von Kosten und Nutzen.

Der Punkt ist, dass es immer Ausnahmen geben wird, in denen a gotogerechtfertigt ist, aber nur für einige aufgrund unterschiedlicher Bewertungen. Schade, dass viele Ingenieure Techniken in absichtlicher Ignoranz einfach ausschließen, weil sie sie im Internet lesen, anstatt sich die Zeit zu nehmen, um zu verstehen, warum.

dietbuddha
quelle
Können Sie Artikel in der goto-Erklärung empfehlen, in denen die Kosten näher erläutert werden?
Thys
-1

Ich glaube nicht, dass dieser Fall eine Ausnahme rechtfertigt, da dies wie folgt geschrieben werden kann:

def outerLoop(){
    for (i = 0; i < 10; i++) {
        if( broken?(i) )
          return; // broken
    }
}

def broken?(i){
   for (j = 10; j > 0; j--) {
        if (j == i) {
            return true; // broken
        }
   }
   return false; // not broken
}
ptyx
quelle