it-swarm.com.de

während (wahr) und Schleifen brechen - Anti-Muster?

Betrachten Sie den folgenden Code:

public void doSomething(int input)
{
   while(true)
   {
      TransformInSomeWay(input);

      if(ProcessingComplete(input))
         break;

      DoSomethingElseTo(input);
   }
}

Angenommen, dieser Prozess umfasst eine endliche, aber eingabeabhängige Anzahl von Schritten. Die Schleife ist so konzipiert, dass sie aufgrund des Algorithmus von selbst beendet wird und nicht unbegrenzt ausgeführt werden kann (bis sie durch ein externes Ereignis abgebrochen wird). Da sich der Test, ob die Schleife enden soll, in der Mitte eines logischen Satzes von Schritten befindet, überprüft die while-Schleife selbst derzeit nichts Sinnvolles. Die Prüfung wird stattdessen an der "richtigen" Stelle innerhalb des konzeptionellen Algorithmus durchgeführt.

Mir wurde gesagt, dass dies ein schlechter Code ist, da er anfälliger für Fehler ist, da die Endbedingung nicht von der Schleifenstruktur überprüft wird. Es ist schwieriger herauszufinden, wie Sie die Schleife verlassen würden, und es können Fehler auftreten, da die Unterbrechungsbedingung bei zukünftigen Änderungen möglicherweise umgangen oder versehentlich weggelassen wird.

Der Code könnte nun wie folgt strukturiert sein:

public void doSomething(int input)
{
   TransformInSomeWay(input);

   while(!ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
      TransformInSomeWay(input);
   }
}

Dies dupliziert jedoch einen Aufruf einer Methode im Code, wodurch DRY verletzt wird. Wenn TransformInSomeWay später durch eine andere Methode ersetzt würde, müssten beide Aufrufe gefunden und geändert werden (und die Tatsache, dass es zwei gibt, ist in einem komplexeren Code möglicherweise weniger offensichtlich).

Sie könnten es auch so schreiben:

public void doSomething(int input)
{
   var complete = false;
   while(!complete)
   {
      TransformInSomeWay(input);

      complete = ProcessingComplete(input);

      if(!complete) 
      {
         DoSomethingElseTo(input);          
      }
   }
}

... aber Sie haben jetzt eine Variable, deren einziger Zweck darin besteht, die Bedingungsprüfung in die Schleifenstruktur zu verschieben, und die auch mehrmals überprüft werden muss, um das gleiche Verhalten wie die ursprüngliche Logik bereitzustellen.

Ich für meinen Teil sage, dass angesichts des Algorithmus, den dieser Code in der realen Welt implementiert, der ursprüngliche Code am besten lesbar ist. Wenn Sie es selbst durchgehen würden, würden Sie so darüber nachdenken, und daher wäre es für Personen, die mit dem Algorithmus vertraut sind, intuitiv.

Also, was ist "besser"? Ist es besser, der while-Schleife die Verantwortung für die Bedingungsprüfung zu übertragen, indem die Logik um die Schleife herum strukturiert wird? Oder ist es besser, die Logik auf "natürliche" Weise zu strukturieren, wie dies durch Anforderungen oder eine konzeptionelle Beschreibung des Algorithmus angezeigt wird, obwohl dies bedeuten kann, dass die integrierten Funktionen der Schleife umgangen werden?

32
KeithS

Bleib bei deiner ersten Lösung. Schleifen können sehr kompliziert werden und eine oder mehrere saubere Unterbrechungen können für Sie, jeden anderen, der sich Ihren Code, den Optimierer und die Leistung des Programms ansieht, viel einfacher sein als das Ausarbeiten von if-Anweisungen und hinzugefügten Variablen. Mein üblicher Ansatz ist es, eine Schleife mit so etwas wie "while (true)" einzurichten und die bestmögliche Codierung zu erhalten. Oft kann und kann ich dann die Unterbrechung (en) durch eine Standardschleifensteuerung ersetzen; Die meisten Loops sind schließlich ziemlich einfach. Die Endbedingung sollte wird von der Schleifenstruktur aus den von Ihnen genannten Gründen überprüft, aber nicht auf Kosten des Hinzufügens ganz neuer Variablen, des Hinzufügens von if-Anweisungen und des Wiederholens von Code. alle von denen sind sogar mehr fehleranfällig.

14
RalphChapin

Es ist nur dann ein sinnvolles Problem, wenn der Schleifenkörper nicht trivial ist. Wenn der Körper so klein ist, ist es trivial und überhaupt kein Problem, ihn so zu analysieren.

13
DeadMG

Ich habe Probleme, die anderen Lösungen besser zu finden als die mit Pause. Nun, ich bevorzuge den Ada-Weg, der es erlaubt

loop
  TransformInSomeWay(input);
exit when ProcessingComplete(input);
  DoSomethingElseTo(input);
end loop;

die Pausenlösung ist jedoch das sauberste Rendering.

Mein POV ist, dass wenn eine Kontrollstruktur fehlt, die sauberste Lösung darin besteht, sie mit anderen Kontrollstrukturen zu emulieren, ohne den Datenfluss zu verwenden. (Und in ähnlicher Weise, wenn ich ein Datenflussproblem habe, reine Datenflusslösungen der mit Kontrollstrukturen vorzuziehen *).

Es ist schwieriger herauszufinden, wie Sie die Schleife verlassen würden.

Verwechseln Sie sich nicht, die Diskussion würde nicht stattfinden, wenn die Schwierigkeit nicht größtenteils dieselbe wäre: Der Zustand ist der gleiche und am selben Ort vorhanden.

Welche von

while (true) {
   XXXX
if (YYY) break;
   ZZZZ;
}

und

while (TTTT) {
    XXXX
    if (YYYY) {
        ZZZZ
    }
}

die beabsichtigte Struktur besser machen? Ich stimme für das erste, Sie müssen nicht überprüfen, ob die Bedingungen übereinstimmen. (Aber siehe meine Einrückung).

7
AProgrammer

während (wahr) und Pause ist eine gängige Redewendung. Dies ist die kanonische Methode, um Mid-Exit-Schleifen in C-ähnlichen Sprachen zu implementieren. Das Hinzufügen einer Variablen zum Speichern der Exit-Bedingung und eines if () um die zweite Hälfte der Schleife ist eine sinnvolle Alternative. Einige Geschäfte können das eine oder andere offiziell standardisieren, aber keines ist überlegen. sie sind gleichwertig.

Ein guter Programmierer, der in diesen Sprachen arbeitet, sollte auf einen Blick wissen, was los ist, wenn er eine der beiden sieht. Es könnte eine gute kleine Interviewfrage stellen; Geben Sie jemandem zwei Schleifen, eine mit jeder Redewendung, und fragen Sie ihn, was der Unterschied ist (richtige Antwort: "nichts", vielleicht mit dem Zusatz "aber ich benutze gewöhnlich DIESE").

3
mjfgates

Ihre Alternativen sind nicht so schlecht, wie Sie vielleicht denken. Guter Code sollte:

  1. Geben Sie mit guten Variablennamen/Kommentaren deutlich an, unter welchen Bedingungen die Schleife beendet werden soll. (Der Bruchzustand sollte nicht ausgeblendet werden)

  2. Geben Sie deutlich an, in welcher Reihenfolge die Vorgänge ausgeführt werden sollen. Hier ist es eine gute Idee, die optionale 3. Operation (DoSomethingElseTo(input)) in das if einzufügen.

Trotzdem ist es leicht, perfektionistische Instinkte beim Messingpolieren viel Zeit in Anspruch nehmen zu lassen. Ich würde nur das if optimieren, wie oben erwähnt; Kommentieren Sie den Code und fahren Sie fort.

2
Pat

Die Leute sagen, es sei eine schlechte Praxis, while (true) zu verwenden, und die meiste Zeit haben sie Recht. Wenn Ihre while -Anweisung viel komplizierten Code enthält und es unklar ist, wann Sie aus if ausbrechen, bleibt Ihnen der Code schwer zu pflegen, und ein einfacher Fehler kann eine Endlosschleife verursachen.

In Ihrem Beispiel verwenden Sie zu Recht while(true), da Ihr Code klar und leicht verständlich ist. Es macht keinen Sinn, ineffizienten und schwerer zu lesenden Code zu erstellen, einfach weil manche Leute es für immer schlecht halten, while(true) zu verwenden.

Ich hatte ein ähnliches Problem:

$i = 0
$key = $str.'0';
$key1 = $str1.'0';
while (isset($array][$key] || isset($array][$key1])
{
    if (isset($array][$key]))
    {
        // Code
    }
    else
    {
        // Code
    }
    $key = $str.++$i;
    $key1 = $str1.$i;
}

Durch die Verwendung von do ... while(true) wird vermieden, dass isset($array][$key] Unnötig zweimal aufgerufen wird. Der Code ist kürzer, sauberer und leichter zu lesen. Es besteht absolut kein Risiko, dass am Ende ein Endlosschleifenfehler mit else break; Eingeführt wird.

$i = -1;
do
{
    $key = $str.++$i;
    $key1 = $str1.$i;
    if (isset($array[$key]))
    {
        // Code
    }
    elseif (isset($array[$key1]))
    {
        // Code
    }
    else
        break;
} while (true);

Es ist gut zu folgen gute Praktiken und zu vermeiden schlechte Praktiken, aber es gibt immer Ausnahmen und es ist wichtig, offen zu bleiben und diese Ausnahmen zu erkennen.

2
Dan Bray

Wenn ein bestimmter Kontrollfluss natürlich als Unterbrechungsanweisung ausgedrückt wird, ist es im Wesentlichen nie eine bessere Wahl, andere Flusssteuerungsmechanismen zum Emulieren einer Unterbrechungsanweisung zu verwenden.

(Beachten Sie, dass dies das teilweise Abrollen der Schleife und das Herunterschieben des Schleifenkörpers umfasst, sodass der Beginn der Schleife mit dem bedingten Test zusammenfällt.)

Wenn Sie Ihre Schleife so neu organisieren können, dass der Kontrollfluss natürlich durch eine bedingte Überprüfung am Anfang der Schleife ausgedrückt wird, ist das großartig. Es könnte sich sogar lohnen, einige Zeit darüber nachzudenken, ob dies möglich ist. Aber versuchen Sie nicht, einen quadratischen Stift in ein rundes Loch zu stecken.

0
user122173

Wenn die Komplexität der Logik unhandlich wird, ist die Verwendung einer unbegrenzten Schleife mit Unterbrechungen in der Mitte der Schleife zur Flusssteuerung tatsächlich am sinnvollsten. Ich habe ein Projekt mit Code, der wie folgt aussieht. (Beachten Sie die Ausnahme am Ende der Schleife.)

L: for (;;) {
    if (someBool) {
        someOtherBool = doSomeWork();
        if (someOtherBool) {
            doFinalWork();
            break L;
        }
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
            break L;
        }
        someOtherBool3 = doSomeWork3();
        if (someOtherBool3) {
            doFinalWork3();
            break L;
        }
    }
    bitOfData = getTheData();
    throw new SomeException("Unable to do something for some reason.", bitOfData);
}
progressOntoOtherThings();

Um diese Logik ohne unbegrenztes Durchbrechen der Schleife auszuführen, würde ich am Ende Folgendes haben:

void method() {
    if (!someBool) {
        throwingMethod();
    }
    someOtherBool = doSomeWork();
    if (someOtherBool) {
        doFinalWork();
    } else {
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
        } else {
            someOtherBool3 = doSomeWork3();
            if (someOtherBool3) {
                doFinalWork3();
            } else {
                throwingMethod();
            }
        }
    }
    progressOntoOtherThings();
}
void throwingMethod() throws SomeException {
        bitOfData = getTheData();
        throw new SomeException("Unable to do something for some reason.", bitOfData);
}

Die Ausnahme wird jetzt in einer Hilfsmethode ausgelöst. Auch hat es ziemlich viel if ... else Verschachtelung. Ich bin mit diesem Ansatz nicht zufrieden. Daher denke ich, dass das erste Muster am besten ist.

0
intrepidis

Sie könnten auch damit gehen:

public void doSomething(int input)
{
   while(TransformInSomeWay(input), !ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
   }
}

Oder weniger verwirrend:

bool TransformAndCheckCompletion(int &input)
{
   TransformInSomeWay(input);
   return ProcessingComplete(input))
}

public void doSomething(int input)
{
   while(TransformAndCheckCompletion(input))
   {
      DoSomethingElseTo(input);
   }
}
0
Eclipse