Sind "break" und "continue" schlechte Programmierpraktiken?

191

Mein Chef erwähnt immer wieder nonchalant, dass schlechte Programmierer breakund continuein Schleifen arbeiten.

Ich benutze sie die ganze Zeit, weil sie einen Sinn ergeben. Lassen Sie sich von mir inspirieren:

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 tatsächliche Funktionalität ausführt. IMO gilt das gleiche mit 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, das erleichtert das Lesen & Debuggen; aber ich sehe auch keinen Nachteil.

Mikhail
quelle
2
Es braucht nicht viel, um zu vergessen, wer was tut.
57
Weder ist goto. Zu wissen, wann man sie benutzt, ist der Schlüssel. Sie sind Werkzeuge in der Toolbox. Sie verwenden sie, wenn sie klaren und prägnanten Code enthalten.
oder
67
Ich kann meine Unterstützung für diesen Codierungsstil nicht stark genug ausdrücken. Mehrere Ebenen verschachtelter Bedingungen sind so viel schlimmer als dieser Ansatz. Ich bin normalerweise nicht militant in Bezug auf den Codierungsstil, aber dies ist für mich fast ein Deal-Breaker.
Emil H
9
Offensichtlich schreibt Ihr Chef nicht genug Code. Wenn ja, würde er wissen, dass alle Schlüsselwörter (ja, sogar goto) in einigen Fällen nützlich sind.
Sakisk
57
Schlechte Programmierer verwenden break and continue , was nicht heißt, dass gute Programmierer dies nicht tun. Schlechte Programmierer verwenden if und while ebenfalls.
Mouviciel

Antworten:

240

Wenn sie zu Beginn eines Blocks verwendet werden, wirken sie wie Vorbedingungen, wenn die ersten Überprüfungen durchgeführt werden. Das ist also gut.

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

Klaim
quelle
6
@Klaim: Es kann argumentiert werden, dass jede Routine mit mehreren Austrittspunkten eine Routine mit schlechten Faktoren ist. Eine Routine mit angemessenen Faktoren sollte nur eines und nur eines tun.
Bit-Twiddler
79
@ Bit-Twiddler: Dies ist eine sehr C-ische Denkweise. Eine temporäre Variable kann zu einem späteren Zeitpunkt geändert werden, sodass ein einzelner Tippfehler in 20 Zeilen Entfernung dieses sorgfältig ausgearbeitete Ergebnis löschen kann. Eine sofortige Rückkehr (oder Pause oder Fortsetzung) ist jedoch äußerst klar: Ich kann jetzt aufhören zu lesen, weil ich weiß, dass es möglicherweise nicht weiter unten geändert werden kann . Das ist gut für mein kleines Gehirn und erleichtert es mir wirklich, durch den Code zu stapfen.
Matthieu M.
15
@ Matthieu Ich stimme zu. Beenden Sie einen Block, wenn Sie ein Ergebnis erhalten, das den Zweck des Blocks erfüllt.
Evan Plaice
8
@ bit-twiddler - das Single-Exit-Point-Prinzip unterscheidet sich vom Single-Responsibility-Prinzip. Ich bin nicht damit einverstanden, dass die Überprüfung immer von der alleinigen Verantwortung des WRT getrennt ist. Tatsächlich erscheint mir "einzelne Verantwortung" immer als subjektiver Begriff. Sollte beispielsweise bei einem quadratischen Löser die Berechnung der Diskriminante eine separate Aufgabe sein? Oder kann die ganze quadratische Formel eine einzelne Verantwortung sein? Ich würde argumentieren, dass es davon abhängt, ob Sie eine separate Verwendung für den Diskriminanten haben - andernfalls ist es wahrscheinlich übertrieben, ihn als separate Verantwortung zu behandeln.
Steve314
10
Diese Antwort ist eine Faustregel, keine harte Regel. Es funktioniert in den meisten Fällen, zögern Sie nicht, es zu brechen, wenn es in Ihrem Kontext Sinn macht.
Fordern Sie den
87

Sie können Donald Knuths 1974 erschienenen Artikel Structured Programming with go to Statements lesen , in dem er verschiedene go tostrukturell wünschenswerte Verwendungen des beschreibt . Sie enthalten das Äquivalent von breakund continueAnweisungen (viele der darin enthaltenen Verwendungen go towurden zu eingeschränkteren Konstrukten entwickelt). Ist Ihr Chef der Typ, der Knuth einen schlechten Programmierer nennt?

(Die angegebenen Beispiele interessieren mich. Typischerweise breakund continuewerden von Leuten abgelehnt, die einen Ein- und Ausstieg aus einem Code-Teil mögen, und diese Art von Person runzelt auch die Stirn bei mehreren returnAussagen.)

David Thornley
quelle
8
Die meisten Leute, die Funktionen und Prozeduren mit einzelnen Ein- und Ausstiegspunkten bevorzugen, sind auf Pascal aufgewachsen. Pascal war nicht die erste Sprache, die ich lernte, aber es hatte einen tiefgreifenden Einfluss darauf, wie ich Code bis heute strukturiere. Die Leute kommentieren immer, wie einfach es ist, meinen Java-Code zu lesen. Das liegt daran, dass ich mehrere Exit-Punkte sowie das Vermischen von Deklarationen mit Code vermeide. Ich gebe mein Bestes, um jede in einer Methode verwendete lokale Variable oben in der Methode zu deklarieren. Diese Praxis vermeidet das Durcheinander von Code, indem ich gezwungen werde, die Methoden kurz und bündig zu halten.
Bit-Twiddler
5
Pascal hatte auch verschachtelte Funktionen. Sag einfach ...
Shog9
6
Soweit ich mich erinnere, war der Hauptgrund, dass die Leute mehrere return-Anweisungen in Funktionen nicht mochten, der, dass Debugger sie nicht richtig handhabten. Es war sehr mühsam, am Ende einer Funktion einen Haltepunkt zu setzen, aber Sie haben ihn aufgrund einer früheren return-Anweisung nie erreicht. Für jeden Compiler, den ich heutzutage verwende, ist das kein Problem mehr.
Dunk
28
@bit-twiddler: da bin ich mir nicht so sicher. Ich benutze Pascal noch heute und betrachte "Single Entry Single Exit" oder zumindest den Single-Exit-Teil davon als Frachtkultprogrammierung. Ich halte gerade Break, Continueund Exitals Werkzeuge in meinem Werkzeugkasten; Ich benutze sie dort, wo es das Verfolgen des Codes erleichtert, und benutze sie nicht dort, wo es das Lesen erschweren würde.
Mason Wheeler
2
@bit-twiddler: amen dazu. Ich werde auch hinzufügen, dass mehrere Austrittspunkte weitaus weniger störend sind, sobald Sie sich auf Blöcke beschränken, die problemlos auf den Bildschirm passen.
Shog9
44

Ich glaube nicht, dass sie schlecht sind. Die Idee, dass sie schlecht sind, stammt aus der Zeit der strukturierten Programmierung. Es hängt mit der Vorstellung zusammen, dass eine Funktion einen einzelnen Eintrittspunkt und einen einzelnen Austrittspunkt haben muss, dh nur einen returnpro 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 verworrenen Logik führen.

Wenn Ihre Funktion sehr kurz ist, wenn Sie eine einzelne Schleife haben oder im schlimmsten Fall zwei verschachtelte Schleifen, und wenn der Schleifenkörper sehr kurz ist, ist es sehr klar, was a breakoder a continuetut. Es ist auch klar, was mehrere returnAnweisungen bewirken.

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

Dima
quelle
12
"Machen Sie Ihre Funktionen klein. Dann machen Sie sie kleiner" -Robert C. Martin. Ich fand, dass dies überraschend gut funktioniert. Jedes Mal, wenn Sie einen Codeblock in einer Funktion sehen, für den ein Kommentar zur Funktionsweise erforderlich ist, schließen Sie ihn in eine separate Funktion mit einem beschreibenden Namen ein. Auch wenn es nur ein paar Zeilen sind und auch wenn es nur einmal verwendet wird. Diese Vorgehensweise beseitigt die meisten Probleme mit break / continue oder Mehrfachrückgaben.
Dima
2
@Mikhail: Die zyklomatische Komplexität ist im Allgemeinen ziemlich stark mit dem SLOC korreliert, was bedeutet, dass der Rat vereinfacht werden kann, um "keine langen Funktionen zu schreiben".
John R. Strohm
3
Die Idee eines einzelnen Austrittspunkts wird häufig falsch interpretiert. Es war einmal, dass Funktionen ihren Anrufer nicht zurückgeben mussten. Sie könnten an einen anderen Ort zurückkehren. Dies wurde üblicherweise in Assemblersprache durchgeführt. Fortran hatte ein spezielles Konstrukt dafür; Sie könnten eine Anweisungsnummer übergeben, der ein kaufmännisches Und vorangestellt ist CALL P(X, Y, &10), und im Fehlerfall könnte die Funktion die Steuerung an diese Anweisung übergeben, anstatt zum Punkt des Aufrufs zurückzukehren.
Kevin Cline
@kevincline wie zum Beispiel bei Lua.
Qix
1
@ cjsimon du hast es. Nicht nur Funktionen sollten klein sein. Klassen sollten auch klein sein.
Dima
39

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

Wenn Sie break and continue häufig verwenden, ist es schwierig, dem Code zu folgen. Wenn es jedoch schwieriger wird, den Code zu ersetzen, 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.

Matthew Read
quelle
Ja, ich habe die Anzahl der Bedingungen übertrieben, um Beispiele für zu beendende Fälle zu liefern.
Mikhail
9
Was ist ein Beispiel für den von Ihnen vorgeschlagenen Ersatzcode? Ich dachte, es sei ein ziemlich vernünftiges Beispiel für Wachaussagen.
Simgineer
Es scheint mir, dass "die klarste mögliche Lösung" immer ... möglich ist? Wie könnte es keine "klarste" Lösung geben? Aber dann bin ich keiner für Absolutes, also hast du vielleicht recht.
@nocomprende Ich bin mir nicht sicher, worauf du hinaus willst. Das "Mögliche" bedeutet hier nicht, dass die beste Lösung nicht existiert - nur, dass perfekte, ultimative Klarheit keine Sache ist. Immerhin ist es subjektiv.
Matthew Read
22

Die meisten Leute halten es für eine schlechte Idee, weil das Verhalten nicht leicht vorhersehbar ist. Wenn Sie den Code durchlesen und while(x < 1000){}davon ausgehen, dass er bis x> = 1000 ausgeführt wird ... Wenn sich jedoch Pausen in der Mitte befinden, stimmt dies nicht, sodass Sie Ihrem Code nicht wirklich vertrauen können Looping ...

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

Wenn ich eine Schleife machen würde, die unter mehr als einer Bedingung unterbrochen wurde, würde ich while(x){}X auf false setzen, wenn ich ausbrechen musste. Das Endergebnis wäre dasselbe, und jeder, der den Code durchliest, würde wissen, dass er die Dinge, die den Wert von X veränderten, genauer betrachten muss.

Satanicpuppy
quelle
2
+1 sehr gut gesagt, und +1 (wenn ich noch einen machen könnte) für die while(notDone){ }Annäherung.
FrustratedWithFormsDesigner
5
Mikhail: Das Problem mit break ist, dass die Endbedingung für die Schleife nie einfach an einer Stelle angegeben wird. Das macht es schwierig, die Nachbedingung nach der Schleife vorherzusagen. In diesem einfachen Fall (> = 1000) ist es nicht schwer. Wenn Sie viele if-Anweisungen und verschiedene Verschachtelungsebenen hinzufügen, kann es sehr, sehr schwierig werden, die Nachbedingung der Schleife zu bestimmen.
S.Lott
S.Lott traf den Nagel genau auf den Kopf. Der Ausdruck, der die Iteration steuert, sollte alle Bedingungen enthalten, die erfüllt sein müssen, um die Iteration fortzusetzen.
Bit-Twiddler
6
Durch Ersetzen break;durch x=false;wird Ihr Code nicht klarer. Sie müssen den Body noch nach dieser Aussage durchsuchen. Und im Falle von müssen x=false;Sie überprüfen, dass es nicht x=true;weiter unten trifft .
Sjoerd
14
Wenn Leute sagen "Ich sehe x und ich nehme y an, aber wenn du z tust, gilt diese Annahme nicht", dann neige ich dazu zu denken "also mache diese dumme Annahme nicht". Viele Leute würden das vereinfachen, um "wenn ich sehe, while (x < 1000)gehe ich davon aus, dass es 1000-mal ausgeführt wird". Nun, es gibt viele Gründe, warum das falsch ist, auch wenn xes anfänglich null ist. Wer sagt zum Beispiel, dass xwährend der Schleife genau einmal inkrementiert und niemals auf andere Weise geändert wird? Selbst x >= 1000wenn Sie davon ausgehen, dass eine bestimmte Menge nicht das Ende der Schleife bedeutet, wird sie möglicherweise in den Bereich zurückgesetzt, bevor die Bedingung überprüft wird.
Steve314
14

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 Codeduplizierungen einführen, die das Verständnis des Programms in der Regel erschweren. Pascal (die Programmiersprache) war aus diesem Grund besonders für Anfängerprogrammierer 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 Ergebnis der Informatik namens Kosarajus Hierarchie der Kontrollstrukturen, das aus dem Jahr 1973 stammt und in Knuths (berühmterem) Aufsatz über GOTOS aus dem Jahr 1974 erwähnt wird. (Dieser Aufsatz von 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 Unterbrechungen der Tiefe n in Programme mit einer Unterbrechungstiefe von weniger als n umzuschreiben, ohne zusätzliche Variablen einzuführen. Aber sagen wir, 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 eine neuere Veröffentlichung von Eric S. Roberts aus dem Jahr 1995 mit dem Titel Loop Exits and Structured Programming: Reopening the Debate ( 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. Wenn zum Beispiel eine Gruppe von Schülern 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 zu den Schülern, die einen break / return / goto-Befehl verwendeten, um das Array zu verlassen die sequentielle Suchschleife, wenn das Element gefunden wurde:

Ich habe noch keine Person gefunden, die versucht hat, ein Programm mit [diesem Stil] zu erstellen, das eine falsche Lösung hervorgebracht 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, schafften es, korrekte Lösungen zu generieren. Diese Zahl entspricht einer Erfolgsquote von weniger als 20%.

Ja, Sie sind vielleicht erfahrener als CS101-Studenten, aber ohne die break-Anweisung (oder gleichwertig return / goto from the middle of loops) zu verwenden, schreiben Sie irgendwann Code, der zwar nominell schön strukturiert ist, aber in Bezug auf zusätzliche Logik haarig genug ist Variablen und Code-Vervielfältigung, die jemand, wahrscheinlich Sie selbst, mit Logikfehlern behebt, 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 deckt ein engeres Thema ab. Sie könnten es wahrscheinlich sogar Ihrem Chef empfehlen, selbst wenn er eher das Management als der CS-Typ ist.

user3588161
quelle
2
Obwohl strukturiertes Programmieren mein Ansatz für viele Jahre war, haben sich in den letzten Jahren die meisten dazu entschlossen, explizite Exits bei der ersten möglichen Gelegenheit zu verwenden. Dies beschleunigt die Ausführung und eliminiert fast alle Logikfehler, die zu Endlosschleifen führten (hängen bleiben).
DocSalvager
9

Ich denke nicht darüber nach, eine dieser schlechten Methoden zu verwenden, aber wenn Sie sie zu oft in derselben Schleife verwenden, sollte die Logik, die in der Schleife verwendet wird, überdacht werden. Verwenden Sie sie sparsam.

Bernard
quelle
:) ja, das Beispiel, das ich zur Verfügung gestellt habe, war konzeptionell
Mikhail
7

Das Beispiel, das Sie gegeben haben, benötigt keine Pausen und fährt auch nicht fort:

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 der gleichen Ebene sind, aber unterschiedliche Aufgaben haben: 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.

Aber wenn Sie tief im Inneren einen Grund finden würden, die Schleife zu stoppen (außer der Primärbedingung), wäre eine Unterbrechung oder Rückkehr sinnvoll. Ich würde das der Verwendung eines zusätzlichen Flags vorziehen, das im Top-Level-Zustand getestet werden soll. Die Pause / Rückkehr ist direkter; Es gibt die Absicht besser an, als eine weitere Variable zu setzen.

Peter Frings
quelle
+1 Aber eigentlich müssen Ihre <Vergleiche <=mit der OPs-Lösung
übereinstimmen
3
In den meisten Fällen ist eine Funktion / Methode zu komplex, wenn man so tief ist, dass man break / return verwenden muss, um die Flusssteuerung zu verwalten.
Bit Twiddler
6

Die "Schlechtigkeit" hängt davon ab, wie Sie sie verwenden. In der Regel verwende ich Unterbrechungen in Schleifenkonstrukten NUR dann, wenn sie mir Zyklen ersparen, die durch ein Refactoring eines Algorithmus nicht gespeichert werden können. Beispiel: Durchsuchen Sie eine Sammlung nach einem Element, dessen Wert in einer bestimmten Eigenschaft auf true festgelegt ist. Wenn Sie nur wissen müssen, dass für eines der Elemente diese Eigenschaft auf true festgelegt wurde, ist eine Unterbrechung hilfreich, um die Schleife entsprechend zu beenden.

Wenn die Verwendung einer Unterbrechung das Lesen des Codes nicht besonders erleichtert, die Ausführung verkürzt oder die Verarbeitungszyklen erheblich spart, sollten Sie sie am besten nicht verwenden. Ich neige dazu, auf den "kleinsten gemeinsamen Nenner" zu codieren, wenn dies möglich ist, um sicherzustellen, dass jeder, der mir folgt, meinen Code leicht einsehen und herausfinden kann, was los ist (ich bin dabei nicht immer erfolgreich). Pausen reduzieren das, weil sie ungerade Ein- / Ausstiegspunkte einführen. Missbräuchlich können sie sich sehr ähnlich verhalten wie eine aus dem Ruder gelaufene Aussage.

Joel Etherton
quelle
Ich stimme beiden Punkten zu! Was Ihren zweiten Punkt betrifft - ich denke, es ist einfacher, meinem ursprünglichen Beitrag zu folgen, da er sich wie Englisch liest. Wenn Sie eine Kombination von Bedingungen in einer if-Anweisung haben, ist es fast eine Entschlüsselung, herauszufinden, was passieren muss, damit das if true ausführt.
Mikhail
@Mikhail: Die Samples, die Sie zur Verfügung gestellt haben, sind für den konkreten Realismus etwas altruistisch. Aus meiner Sicht sind diese Beispiele klar, prägnant und leichter zu lesen. Die meisten Loops sind nicht so. Die meisten Loops haben eine andere Logik und möglicherweise viel kompliziertere Bedingungen. In diesen Fällen ist break / continue möglicherweise nicht die beste Verwendung, da es die Logik beim Lesen durcheinander bringt.
Joel Etherton
4

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

Aber die Verwendung von Aussagen wie breakund continueist heutzutage absolut notwendig und keinesfalls als schlechte Programmierpraxis anzusehen.

Und auch nicht so schwer zu verstehen, den Kontrollfluss bei der Verwendung von breakund continue. In Konstrukten wie diesem ist switchdie breakAnweisung unbedingt erforderlich.

Radheshyam Nayak
quelle
2
Ich habe "continue" nicht mehr verwendet, seit ich C 1981 zum ersten Mal gelernt habe. Es ist eine unnötige Sprachfunktion, da der Code, der von der continue-Anweisung umgangen wird, von einer bedingten Steueranweisung umbrochen werden kann.
Bit Twiddler
12
In diesen Fällen bevorzuge ich die Verwendung von continue, da dadurch sichergestellt wird, dass mein Code nicht zum Pfeilcode wird. Ich hasse Pfeilcode mehr als goto Typanweisungen. Ich las es auch als: "Wenn diese Aussage wahr ist, überspringe den Rest dieser Schleife und fahre mit der nächsten Iteration fort." Sehr nützlich, wenn es sich ganz am Anfang einer for-Schleife befindet (weniger nützlich in while-Schleifen).
Sternberg
@jsternberg Für den Sieg! :-)
Notinlist
3

Der Grundgedanke ist, dass Sie Ihr Programm semantisch analysieren können. Wenn Sie einen einzelnen Eingang und einen einzelnen Ausgang haben, ist die Berechnung möglicher Zustände erheblich einfacher als wenn Sie Gabelpfade verwalten müssen.

Zum Teil spiegelt sich diese Schwierigkeit darin wider, dass Sie in der Lage sind, konzeptionell über Ihren Code nachzudenken.

Ehrlich gesagt ist Ihr zweiter Code nicht offensichtlich. Was macht es? Geht 'weiter' oder 'weiter' die Schleife? Ich habe keine Ahnung. Zumindest Ihr erstes Beispiel ist klar.

Paul Nathan
quelle
Wenn ich in einem Projekt arbeite, zu dessen Verwendung der Manager verpflichtet ist, musste ich ein Flussdiagramm verwenden, um mich an dessen Verwendung zu erinnern. Die verschiedenen Exit-Pfade haben den Code verwirrender gemacht ...
umlcat
Ihr "ehrlich" -Kommentar ist syntaktisch - "next" ist eine Perl-Sache; Normale Sprachen verwenden "Weiter", um "diese Schleife überspringen" zu bedeuten
Mikhail
@Mik, vielleicht wäre "diese Iteration überspringen" eine bessere Beschreibung. Mit freundlichen Grüßen Dr. IM Pedantic
Pete Wilson
@Mikhail: Sicher. Aber wenn man in vielen Sprachen arbeitet, kann es zu Fehlern kommen, wenn die Syntax nicht für alle Sprachen gleich ist.
Paul Nathan
Aber Mathe programmiert nicht . Code ist ausdrucksvoller als Mathe. Ich erhalte die Meldung , dass ein einzelner Eintrag / ein einzelner Ausgang Flussdiagramme schöner aussehen lässt, aber zu welchem ​​Preis (d. H. Wo kann ein Umbruch / eine Rückkehr die Leistung von Code verbessern)?
Evan Plaice
3

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 Kürze - ich denke eigentlich, das ist einfacher zu lesen und für jemanden zu verstehen, was los ist. Im Allgemeinen sollten die Bedingungen für Ihre Schleifen nur in den Schleifenbedingungen enthalten sein, die nicht über den gesamten Körper verteilt sind. Es gibt jedoch einige Situationen , in denen breakund continueLesbarkeit helfen können. breakmehr, als continueich hinzufügen könnte: D

El Ronnoco
quelle
Solange ich nicht damit einverstanden bin, "denke ich, dass dies einfacher zu lesen ist", erfüllt dies genau den gleichen Zweck wie der obige Code. Ich habe bis jetzt noch nie wirklich an "Pause" als Kurzschlussbetreiber gedacht, aber es macht vollkommen Sinn. Was tun Sie, wenn Sie eine foreach-Schleife verarbeiten, da es wirklich keine Möglichkeit gibt, bedingte Logik in die Schleifendefinition einzufügen?
Evan Plaice
@Evan Die Bedingung gilt nicht für eine foreachSchleife, da hierdurch nur alle Elemente in einer Sammlung durchlaufen werden . Eine forSchleife ist insofern ähnlich, als sie keinen bedingten Endpunkt haben sollte. Wenn Sie einen bedingten Endpunkt benötigen, müssen Sie eine whileSchleife verwenden.
El Ronnoco
1
@Evan Ich verstehe Ihren Standpunkt jedoch - zB 'Was ist, wenn Sie aus einer foreach-Schleife ausbrechen müssen?' - Nun, es sollte breakmeiner Meinung nach nur ein Maximum aus der Schleife geben.
El Ronnoco
2

Ich bin nicht einverstanden mit Ihrem Chef. Es gibt geeignete Orte für breakund continuezum Gebrauch. Der Grund, warum Ausnahmen und Ausnahmebehandlungen in modernen Programmiersprachen eingeführt wurden, ist, dass Sie nicht jedes Problem mit nur lösen können structured techniques.

Nebenbei möchte ich 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 Seite beachten

Ich persönlich mag die Verwendung von ( flag == true )in conditionals 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, Sie sind sich sicher, dass Ihr Compiler dies tut optimieren Sie diesen zusätzlichen Vergleich weg.

Zeke Hansell
quelle
Ich habe jahrelang über diese Frage gestritten. Dein Weg ('if (flag) {...}') ist viel knapper und sieht vielleicht eher 'professionell' oder 'sachkundig' aus. Aber, weißt du, es bedeutet oft, dass ein Leser / Betreuer sich kurz unterbrechen muss, um sich daran zu erinnern, was das Konstrukt bedeutet. und um den Sinn des Tests zu verstehen. Derzeit verwende ich 'if (flag == true) {...}', nur weil es eine bessere Dokumentation zu sein scheint. Nächsten Monat? Quien sabe?
Pete Wilson
2
@Pete - Der Begriff ist nicht knapp, sondern elegant . Sie können alles waffeln, was Sie wollen, aber wenn Sie befürchten, dass der Leser / Betreuer nicht versteht, was ein booleanist oder was die knappe / elegante Terminologie bedeutet, sollten Sie vielleicht besser ein paar klügere Betreuer einstellen ;-)
Zeke Hansell
@Pete, ich stehe auch zu meiner Aussage über den generierten Code. Sie führen einen weiteren Vergleich durch, indem Sie ein Flag mit einem konstanten Wert vergleichen, bevor Sie den Booleschen Wert des Ausdrucks auswerten. Warum es schwieriger machen, als es sein muss, hat die Flag-Variable bereits den gewünschten Wert!
Zeke Hansell
+1 gute Arbeit. Auf jeden Fall viel eleganter als das vorherige Beispiel.
Evan Plaice
2

Ich stimme deinem Chef zu. Sie sind schlecht, weil sie Methoden mit hoher zyklomatischer Komplexität erzeugen. 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, bei der "continue" zu "return" wird. "Rückkehr" ist besser, weil es nach "Rückkehr" vorbei ist - es gibt keine Sorgen um den lokalen Staat.

Für "break" extrahieren Sie 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 darauf, dass eine Klasse extrahiert werden soll - sammeln Sie sie entweder in einem Kontextobjekt.

Kevin Cline
quelle
0

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

Davidhaskins
quelle
2
Tatsächlich ist es leicht zu erkennen, ob Sie richtig eingerückt sind, die Semantik dieser Anweisungen verinnerlicht haben und nicht zu schläfrig sind.
@delnan - das ist eine Menge Annahmen;)
Davidhaskins
2
Ja, besonders der letzte.
Michael K
Nun, # 1 ist sowieso für ernsthafte Programmierung erforderlich, # 2 ist von jedem zu erwarten, der als Programmierer bezeichnet wird, und # 3 ist im Allgemeinen recht nützlich;)
Einige Sprachen (nur Skripte, die ich kenne) unterstützen break 2; für andere denke ich, dass temporäre Bool-Flags verwendet werden
Mikhail
0

Solange sie nicht wie im folgenden Beispiel getarnt verwendet werden:

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

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

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

SuperBloup
quelle
Würden Sie in solchen Fällen empfehlen, Funktionen zu erstellen?
Mikhail
Ja, und wenn nicht möglich, ein einfacher Sprung. Wenn ich ein do sehe, denke ich, dass Wiederholung, nicht Kontext, um Sprung zu simulieren.
SuperBloup
oh ich stimme zu, ich habe nur gefragt, wie du es machen würdest :) Informatiker machen es wiederholt
Mikhail
0

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 benutze wirklich nicht gerne return, um eine Funktion abzubrechen. Es fühlt sich an wie ein Missbrauch von return.

Mit breakauch ist 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üssten Sie natürlich bessere Namen haben ...)

(Alle obigen Codes sind Pseudocodes)

FrustratedWithFormsDesigner
quelle
11
Sie würden wirklich 3 Verschachtelungsebenen dem vorziehen, was ich oben eingegeben habe?
Mikhail
@Mikhail: Ja, oder ich würde das Ergebnis der Bedingung einer Variablen zuweisen. Ich finde es viel leichter zu verstehen als die Logik von breakund continue. Das ungewöhnliche Beenden einer Schleife fühlt sich einfach komisch an und ich mag es nicht.
FrustratedWithFormsDesigner
1
Ironischerweise haben Sie meine Bedingungen falsch verstanden. continuebedeutet, die Funktionalität überspringen, um zur nächsten Schleife zu gelangen; nicht "mit der Hinrichtung fortfahren"
Mikhail
@Mikhail: Ah. Ich benutze es nicht oft und wenn ich es lese, verwirrt mich die Bedeutung. Ein weiterer Grund, warum ich es nicht mag. : P Geben Sie mir eine Minute Zeit zum Aktualisieren ...
FrustratedWithFormsDesigner
7
Zu viele Schachtelungen beeinträchtigen die Lesbarkeit. Und manchmal führt das Vermeiden der Unterbrechung / Fortsetzung dazu, dass Sie Ihre Logik bei Ihren bedingten Tests umkehren müssen, was zu einer Fehlinterpretation der Code-Aktivitäten führen kann - ich sage es nur
Zeke Hansell,
0

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

Viele gängige Sprachen (Java, .NET (C # + VB), PHP, eigene Sprachen) 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 "break" - und "continue" -Code kürzer ist und "while" -Sätze normalerweise in "for" - oder "foreach" -Sätze umwandelt.

Beide Fälle sind eine Frage des Codierungsstils. Ich bevorzuge es, sie nicht zu verwenden , da ich durch den ausführlichen Stil mehr Kontrolle über den Code habe.

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, aber hypothetisch sind. Wenn wir sie entfernen müssten, müssten wir auch "while" und "do while" entfernen ("repeat until", ihr Pascal-Typen) ;-)

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

umlcat
quelle
Tut mir leid, dass Sie wählerisch sind, aber in Ihrem zweiten Beispiel fehlt die Ausgabe für den Fall, dass der Schlüssel nicht gefunden wird (so sieht es natürlich viel kürzer aus).
FrustratedWithFormsDesigner
2
Ganz zu schweigen davon, dass das erste Beispiel nur funktioniert, wenn der Schlüssel der letzte in der Liste ist.
Mikhail
@FrustratedWithFormsDesigner GENAU. Ich habe absichtlich angegeben, warum diese Methode vorzuziehen ist
;-)
Sie haben jedoch zwei Routinen mit unterschiedlicher Semantik; Daher sind sie nicht logisch äquivalent.
Bit-Twiddler
2
Ihr zweites Beispiel enthält zwei Fehler, einen syntaktischen und einen logischen. 1. Es wird nicht kompiliert, da der Iterator nicht außerhalb des Gültigkeitsbereichs der for-Schleife deklariert ist (daher nicht in der String-Ausgabe verfügbar). 2. Selbst wenn der Iterator außerhalb der Schleife deklariert wurde und der Schlüssel nicht in der Auflistung gefunden wird, gibt die Zeichenfolgenausgabe den Schlüssel des letzten Elements in der Liste aus.
Evan Plaice
0

Ich bin nicht gegen continueund breakim Prinzip, aber ich denke , sie sind sehr Low-Level - Konstrukte , die sehr oft durch etwas noch besser ersetzt werden.

Ich verwende hier C # als Beispiel und betrachte den Fall, dass eine Auflistung durchlaufen werden soll, aber wir möchten nur die Elemente, die ein Prädikat erfüllen, und wir möchten nicht mehr als maximal 100 Iterationen durchfü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 AUSSERGEWÖHNLICH sauber aus. Es ist nicht sehr schwer zu verstehen. Ich denke, dass es eine Menge bringt, wenn man deklarativer ist. Vergleichen Sie es mit den folgenden:

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

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

In einigen Teilen des Codes wird es immer 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.

Kai
quelle
-1

Code Complete enthält einen schönen Abschnitt über die Verwendung gotound mehrfache Rückkehr von Routine oder Schleife.

Im Allgemeinen ist es keine schlechte Praxis. breakoder continuegenau sagen, was als nächstes passiert. Und dem stimme ich zu.

Steve McConnell (Autor von Code Complete) verwendet fast dieselben Beispiele wie Sie, um die Vorteile der Verwendung verschiedener gotoAnweisungen aufzuzeigen.

Überbeanspruchung breakoder kann continuejedoch zu komplexer und nicht zu wartender Software führen.

Grzegorz Gierlik
quelle