it-swarm-eu.dev

Sind "break" und "continue" schlechte Programmierpraktiken?

Mein Chef erwähnt immer wieder nonchalant, dass schlechte Programmierer break und continue in Schleifen verwenden.

Ich benutze sie die ganze Zeit, weil sie Sinn machen; Lassen Sie mich Ihnen die Inspiration zeigen:

function verify(object) {
    if (object->value < 0) return false;
    if (object->value > object->max_value) return false;
    if (object->name == "") return false;
    ...
}

Der Punkt hier ist, dass zuerst die Funktion prüft, ob die Bedingungen korrekt sind, und dann die eigentliche Funktionalität ausführt. IMO gilt das Gleiche für Schleifen:

while (primary_condition) {
    if (loop_count > 1000) break;
    if (time_exect > 3600) break;
    if (this->data == "undefined") continue;
    if (this->skip == true) continue;
    ...
}

Ich denke, dies erleichtert das Lesen und Debuggen. aber ich sehe auch keinen Nachteil.

206
Mikhail

Wenn sie zu Beginn eines Blocks verwendet werden, wirken sie bei ersten Überprüfungen wie Voraussetzungen, also ist es gut.

Wenn sie in der Mitte des Blocks mit etwas Code verwendet werden, verhalten sie sich wie versteckte Fallen, also ist es schlecht.

253
Klaim

Sie könnten Donald Knuths Artikel von 1974 Structured Programming with go to Statements lesen, in dem er verschiedene Verwendungen des go to die strukturell wünschenswert sind. Sie enthalten das Äquivalent der Anweisungen break und continue (viele der Verwendungen von go to darin wurden zu begrenzteren Konstrukten entwickelt). Ist Ihr Chef der Typ, der Knuth als schlechten Programmierer bezeichnet?

(Die angegebenen Beispiele interessieren mich. Normalerweise werden break und continue von Leuten nicht gemocht, die einen Eintrag und einen Ausstieg aus einem Code mögen, und diese Art von Person runzelt auch die Stirn bei mehreren return Anweisungen.)

90
David Thornley

Ich glaube nicht, dass sie schlecht sind. Die Idee, dass sie schlecht sind, stammt aus den Tagen der strukturierten Programmierung. Es hängt mit der Vorstellung zusammen, dass eine Funktion einen einzelnen Eintrittspunkt und einen einzelnen Austrittspunkt haben muss, d.h. e. nur eine return pro Funktion.

Dies ist sinnvoll, wenn Ihre Funktion lang ist und Sie mehrere verschachtelte Schleifen haben. Ihre Funktionen sollten jedoch kurz sein, und Sie sollten Schleifen und ihre Körper in eigene kurze Funktionen einwickeln. Im Allgemeinen kann das Erzwingen eines einzelnen Austrittspunkts für eine Funktion zu einer sehr komplizierten Logik führen.

Wenn Ihre Funktion sehr kurz ist, wenn Sie eine einzelne Schleife oder im schlimmsten Fall zwei verschachtelte Schleifen haben und wenn der Schleifenkörper sehr kurz ist, ist es sehr klar, was eine break oder eine continue tut. Es ist auch klar, was mehrere return -Anweisungen bewirken.

Diese Probleme werden in "Clean Code" von Robert C. Martin und in "Refactoring" von Martin Fowler behandelt.

46
Dima

Schlechte Programmierer sprechen absolut (genau wie Sith). Gute Programmierer verwenden die klarste mögliche Lösung (alle anderen Dinge sind gleich).

Wenn Sie break und continue häufig verwenden, ist es schwierig, dem Code zu folgen. Aber wenn das Ersetzen des Codes das Befolgen des Codes noch schwieriger macht, ist dies eine schlechte Änderung.

Das Beispiel, das Sie gegeben haben, ist definitiv eine Situation, in der die Pausen und Fortsetzungen durch etwas Eleganteres ersetzt werden sollten.

44
Matthew Read

Die meisten Leute halten es für eine schlechte Idee, weil das Verhalten nicht leicht vorhersehbar ist. Wenn Sie den Code lesen und while(x < 1000){} sehen, gehen Sie davon aus, dass er bis x> = 1000 ausgeführt wird. Wenn jedoch in der Mitte Unterbrechungen auftreten, gilt dies nicht Sie können Ihrem Looping nicht wirklich vertrauen ...

Es ist der gleiche Grund, warum die Leute GOTO nicht mögen: sicher, es kann gut verwendet werden, aber es kann auch zu gottesfürchtigem Spaghetti-Code führen, bei dem der Code zufällig von Abschnitt zu Abschnitt springt.

Wenn ich für mich selbst eine Schleife ausführen würde, die unter mehr als einer Bedingung unterbrochen wurde, würde ich while(x){} ausführen und dann X auf false umschalten, wenn ich ausbrechen musste. Das Endergebnis wäre dasselbe, und jeder, der den Code liest, würde sich die Dinge genauer ansehen, die den Wert von X verändert haben.

25
Satanicpuppy

Ja, Sie können Programme ohne break-Anweisungen [neu] schreiben (oder aus der Mitte von Schleifen zurückkehren, die dasselbe tun). Möglicherweise müssen Sie jedoch zusätzliche Variablen und/oder Codeduplikationen einführen, die das Verständnis des Programms normalerweise erschweren. Pascal (die Programmiersprache) war aus diesem Grund besonders für Anfänger sehr schlecht. Ihr Chef möchte im Grunde, dass Sie in Pascals Kontrollstrukturen programmieren. Wenn Linus Torvalds in Ihren Schuhen wäre, würde er Ihrem Chef wahrscheinlich den Mittelfinger zeigen!

Es gibt ein Informatik-Ergebnis namens Kosarajus Hierarchie der Kontrollstrukturen, das bis 1973 zurückreicht und in Knuths (mehr) berühmtem Artikel über Gotos von 1974 erwähnt wird. (Dieser Artikel über Knuth wurde übrigens bereits oben von David Thornley empfohlen .) Was S. Rao Kosaraju 1973 bewiesen hat, ist, dass es nicht möglich ist, alle Programme mit mehrstufigen Tiefenbrüchen n in Programme mit Unterbrechung umzuschreiben Tiefe kleiner als n ohne zusätzliche Variablen einzuführen. Nehmen wir an, das ist nur ein rein theoretisches Ergebnis. (Fügen Sie einfach ein paar zusätzliche Variablen hinzu?! Sicher können Sie das tun, um Ihrem Chef zu gefallen ...)

Was aus Sicht der Softwareentwicklung weitaus wichtiger ist, ist ein neueres Papier von Eric S. Roberts aus dem Jahr 1995 mit dem Titel Loop-Exits und strukturierte Programmierung: Wiedereröffnung der Debatte ( http://cs.stanford.edu/people/eroberts/papers/SIGCSE-1995/LoopExits.pdf ). Roberts fasst mehrere empirische Studien zusammen, die von anderen vor ihm durchgeführt wurden. Als beispielsweise eine Gruppe von Studenten vom Typ CS101 gebeten wurde, Code für eine Funktion zu schreiben, die eine sequentielle Suche in einem Array implementiert, sagte der Autor der Studie Folgendes über diejenigen Studenten, die eine Pause/Rückkehr/Goto verwendeten, um das Programm zu verlassen die sequentielle Suchschleife, als das Element gefunden wurde:

Ich habe noch keine einzige Person gefunden, die ein Programm mit [diesem Stil] versucht hat und eine falsche Lösung gefunden hat.

Roberts sagt auch, dass:

Studenten, die versuchten, das Problem zu lösen, ohne eine explizite Rückkehr aus der for-Schleife zu verwenden, schnitten viel weniger gut ab: Nur sieben der 42 Studenten, die diese Strategie versuchten, gelang es, korrekte Lösungen zu generieren. Diese Zahl entspricht einer Erfolgsquote von weniger als 20%.

Ja, Sie sind vielleicht erfahrener als CS101-Schüler, aber ohne die break-Anweisung (oder gleichwertig return/goto aus der Mitte von Schleifen) zu schreiben, schreiben Sie schließlich Code, der zwar nominell gut strukturiert ist, aber in Bezug auf zusätzliche Logik haarig genug ist Variablen und Codeduplizierungen, die jemand, wahrscheinlich Sie selbst, mit logischen Fehlern versehen, während er versucht, dem Codierungsstil Ihres Chefs zu folgen.

Ich werde hier auch sagen, dass Roberts 'Artikel für den durchschnittlichen Programmierer weitaus zugänglicher ist, also eine bessere erste Lektüre als die von Knuth. Es ist auch kürzer und behandelt ein engeres Thema. Sie könnten es wahrscheinlich sogar Ihrem Chef empfehlen, selbst wenn er eher das Management als der CS-Typ ist.

16
Fizz

Ich denke nicht darüber nach, eine dieser schlechten Praktiken zu verwenden, aber wenn Sie sie zu oft in derselben Schleife verwenden, sollte dies ein Überdenken der in der Schleife verwendeten Logik rechtfertigen. Verwenden Sie sie sparsam.

9
Bernard

Das Beispiel, das Sie gegeben haben, braucht keine Pausen und geht auch nicht weiter:

while (primary-condition AND
       loop-count <= 1000 AND
       time-exec <= 3600) {
   when (data != "undefined" AND
           NOT skip)
      do-something-useful;
   }

Mein "Problem" mit den 4 Zeilen in Ihrem Beispiel ist, dass sie alle auf derselben Ebene sind, aber unterschiedliche Dinge tun: einige brechen, andere fahren fort ... Sie müssen jede Zeile lesen.

In meinem verschachtelten Ansatz wird der Code umso „nützlicher“, je tiefer Sie gehen.

Wenn Sie jedoch tief im Inneren einen Grund finden würden, die Schleife zu stoppen (außer der primären Bedingung), hätte eine Unterbrechung oder Rückkehr ihre Verwendung. Ich würde das der Verwendung eines zusätzlichen Flags vorziehen, das im Zustand der obersten Ebene getestet werden soll. Die Pause/Rückkehr ist direkter; Es gibt die Absicht besser an, als eine weitere Variable festzulegen.

7
Peter Frings

Die "Schlechtigkeit" hängt davon ab, wie Sie sie verwenden. Normalerweise verwende ich Pausen in Schleifenkonstrukten NUR, wenn dadurch Zyklen gespeichert werden, die durch ein Refactoring eines Algorithmus nicht gespeichert werden können. Durchlaufen Sie beispielsweise eine Sammlung, um nach einem Element mit einem Wert in einer bestimmten Eigenschaft zu suchen, der auf true festgelegt ist. Wenn Sie nur wissen müssen, dass für eines der Elemente diese Eigenschaft auf true gesetzt wurde, ist eine Unterbrechung gut, um die Schleife entsprechend zu beenden, sobald Sie dieses Ergebnis erreicht haben.

Wenn die Verwendung einer Unterbrechung den Code nicht besonders lesbar macht, kürzer ausführt oder Zyklen in der Verarbeitung in erheblichem Maße speichert, ist es am besten, sie nicht zu verwenden. Ich neige dazu, wenn möglich auf den "kleinsten gemeinsamen Nenner" zu codieren, um sicherzustellen, dass jeder, der mir folgt, meinen Code leicht einsehen und herausfinden kann, was los ist (dies gelingt mir nicht immer). Pausen reduzieren dies, da sie ungerade Ein-/Ausstiegspunkte einführen. Missbraucht können sie sich sehr wie eine aus dem Ruder laufende "goto" -Aussage verhalten.

6
Joel Etherton

Absolut nicht ... Ja, die Verwendung von goto ist schlecht, da dies die Struktur Ihres Programms verschlechtert und es auch sehr schwierig ist, den Kontrollfluss zu verstehen.

Die Verwendung von Anweisungen wie break und continue ist heutzutage jedoch absolut notwendig und wird überhaupt nicht als schlechte Programmierpraxis angesehen.

Und auch nicht so schwer zu verstehen, welchen Kontrollfluss break und continue verwenden. In Konstrukten wie switch ist die Anweisung break unbedingt erforderlich.

4

Ich würde Ihr zweites Code-Snippet durch ersetzen

while (primary_condition && (loop_count <= 1000 && time_exect <= 3600)) {
    if (this->data != "undefined" && this->skip != true) {
        ..
    }
}

nicht aus Gründen der Knappheit - ich denke tatsächlich, dass dies leichter zu lesen ist und dass jemand versteht, was los ist. Im Allgemeinen sollten die Bedingungen für Ihre Schleifen nur in den Schleifenbedingungen enthalten sein, die nicht im ganzen Körper verteilt sind. Es gibt jedoch Situationen, in denen break und continue die Lesbarkeit verbessern können. break mehr als continue Ich könnte hinzufügen: D.

3
El Ronnoco

Der wesentliche Begriff ergibt sich aus der semantischen Analyse Ihres Programms. Wenn Sie einen einzelnen Eintrag und einen einzelnen Ausgang haben, ist die zur Kennzeichnung möglicher Zustände erforderliche Mathematik erheblich einfacher, als wenn Sie Gabelpfade verwalten müssen.

Zum Teil spiegelt sich diese Schwierigkeit darin wider, dass Sie konzeptionell über Ihren Code nachdenken können.

Ehrlich gesagt ist Ihr zweiter Code nicht offensichtlich. Was macht es? Wird "Weiter" oder "Weiter" in der Schleife fortgesetzt? Ich habe keine Ahnung. Zumindest ist Ihr erstes Beispiel klar.

3
Paul Nathan

Ich stimme Ihrem Chef zu. Sie sind schlecht, weil sie Methoden mit hoher zyklomatischer Komplexität produzieren. Solche Methoden sind schwer zu lesen und schwer zu testen. Zum Glück gibt es eine einfache Lösung. Extrahieren Sie den Schleifenkörper in eine separate Methode, wobei "continue" zu "return" wird. "Rückkehr" ist besser, weil es nach "Rückkehr" vorbei ist - es gibt keine Sorgen um den lokalen Staat.

Extrahieren Sie für "break" die Schleife selbst in eine separate Methode und ersetzen Sie "break" durch "return".

Wenn die extrahierten Methoden eine große Anzahl von Argumenten erfordern, ist dies ein Hinweis zum Extrahieren einer Klasse - entweder sammeln Sie sie in einem Kontextobjekt.

2
kevin cline

Ich bin nicht einverstanden mit Ihrem Chef. Es gibt geeignete Stellen, an denen break und continue verwendet werden können. Der Grund, warum Ausnahmen und Ausnahmebehandlung in moderne Programmiersprachen eingeführt wurden, ist, dass Sie nicht jedes Problem mit nur structured techniques Lösen können.

Nebenbei bemerkt Ich möchte hier keine religiöse Diskussion beginnen, aber Sie könnten Ihren Code so umstrukturieren, dass er noch besser lesbar ist:

while (primary_condition) {
    if (loop_count > 1000) || (time_exect > 3600) {
        break;
    } else if ( ( this->data != "undefined") && ( !this->skip ) ) {
       ... // where the real work of the loop happens
    }
}

Auf einer anderen Randnotiz

Ich persönlich mag die Verwendung von ( flag == true ) In Bedingungen nicht, denn wenn die Variable bereits ein Boolescher Wert ist, führen Sie einen zusätzlichen Vergleich ein, der durchgeführt werden muss, wenn der Wert des Booleschen Werts die gewünschte Antwort hat - es sei denn natürlich, Sie sind sicher, dass Ihr Compiler diesen zusätzlichen Vergleich weg optimieren wird.

2
Zeke Hansell

Ich bin im Prinzip nicht gegen continue und break, aber ich denke, es sind Konstrukte auf sehr niedriger Ebene, die sehr oft durch etwas noch Besseres ersetzt werden können.

Ich verwende hier C # als Beispiel. Betrachten Sie den Fall, dass Sie über eine Sammlung iterieren möchten, aber wir möchten nur die Elemente, die ein Prädikat erfüllen, und wir möchten nicht mehr als maximal 100 Iterationen ausführen.

for (var i = 0; i < collection.Count; i++)
{
    if (!Predicate(item)) continue;
    if (i >= 100) break; // at first I used a > here which is a bug. another good thing about the more declarative style!

    DoStuff(item);
}

Das sieht vernünftig sauber aus. Es ist nicht sehr schwer zu verstehen. Ich denke, es würde viel davon profitieren, wenn man deklarativer wäre. Vergleichen Sie es mit Folgendem:

foreach (var item in collection.Where(Predicate).Take(100))
    DoStuff(item);

Vielleicht sollten die Where and Take-Anrufe nicht einmal in dieser Methode enthalten sein. Möglicherweise sollte diese Filterung durchgeführt werden, bevor die Sammlung an diese Methode übergeben wird. Wenn wir uns von einfachen Dingen entfernen und uns mehr auf die eigentliche Geschäftslogik konzentrieren, wird klarer, woran wir WIRKLICH interessiert sind. Es wird einfacher, unseren Code in zusammenhängende Module zu unterteilen, die sich mehr an gute Designpraktiken halten und so weiter auf.

In einigen Teilen des Codes wird es noch Low-Level-Inhalte geben, aber wir möchten dies so weit wie möglich verbergen, da es mentale Energie erfordert, die wir verwenden könnten, um stattdessen über die geschäftlichen Probleme nachzudenken.

0
sara

Ich denke, es ist nur ein Problem, wenn es tief in mehreren Schleifen verschachtelt ist. Es ist schwer zu wissen, zu welcher Schleife Sie brechen. Es mag schwierig sein, einem Fortfahren zu folgen, aber ich denke, der wahre Schmerz kommt von Pausen - die Logik kann schwierig zu befolgen sein.

0
davidhaskins

Ich mag keinen dieser Stile. Folgendes würde ich bevorzugen:

function verify(object)
{
    if not (object->value < 0) 
       and not(object->value > object->max_value)
       and not(object->name == "") 
       {
         do somethign important
       }
    else return false; //probably not necessary since this function doesn't even seem to be defined to return anything...?
}

Ich mag es wirklich nicht, return zu verwenden, um eine Funktion abzubrechen. Es fühlt sich an wie ein Missbrauch von return.

Die Verwendung von break ist ebenfalls nicht immer klar zu lesen.

Besser noch könnte sein:

notdone := primarycondition    
while (notDone)
{
    if (loop_count > 1000) or (time_exect > 3600)
    {
       notDone := false; 
    }
    else
    { 
        skipCurrentIteration := (this->data == "undefined") or (this->skip == true) 

        if not skipCurrentIteration
        {
           do something
        } 
        ...
    }
}

weniger Verschachtelung und die komplexen Bedingungen werden in Variablen umgestaltet (in einem echten Programm müsste man natürlich bessere Namen haben ...)

(Alle obigen Codes sind Pseudocodes)

Nr. Es ist eine Möglichkeit, ein Problem zu lösen, und es gibt andere Möglichkeiten, es zu lösen.

Viele aktuelle Mainstream-Sprachen (Java, .NET (C # + VB), PHP, schreiben Sie Ihre eigenen) verwenden "break" und "continue", um Schleifen zu überspringen. Sie beide "strukturierte goto (s)" Sätze.

Ohne sie:

String myKey = "mars";

int i = 0; bool found = false;
while ((i < MyList.Count) && (not found)) {
  found = (MyList[i].key == myKey);
  i++;   
}
if (found)
  ShowMessage("Key is " + i.toString());
else
  ShowMessage("Not found.");

Mit ihnen:

String myKey = "mars";

for (i = 0; i < MyList.Count; i++) {
  if (MyList[i].key == myKey)
    break;
}
ShowMessage("Key is " + i.toString());

Beachten Sie, dass der Code "break" und "continue" kürzer ist und normalerweise "while" -Sätze in "for" - oder "foreach" -Sätze umwandelt.

Beide Fälle sind eine Frage des Codierungsstils. Ich bevorzuge es, sie nicht zu verwenden, weil der ausführliche Stil mir mehr Kontrolle über den Code ermöglicht.

Ich arbeite tatsächlich in einigen Projekten, in denen es obligatorisch war, diese Sätze zu verwenden.

Einige Entwickler denken vielleicht, dass sie nicht unbedingt notwendig sind, aber hypothetisch, wenn wir sie entfernen müssten, müssten wir auch "while" und "do while" ("Wiederholung bis", ihr Pascal-Leute) entfernen ;-)

Fazit, auch wenn ich sie lieber nicht benutze, Ich denke, es ist eine Option, keine schlechte Programmierpraxis.

0
umlcat

Solange sie nicht wie im folgenden Beispiel als getarnt verwendet werden:

do
{
      if (foo)
      {
             /*** code ***/
             break;
      }

      if (bar)
      {
             /*** code ***/
             break;
      }
} while (0);

Mir geht es gut mit ihnen. (Beispiel im Produktionscode, meh)

0
SuperBloup