Wenn ich ReSharper für meinen Code ausgeführt habe, zum Beispiel:
if (some condition)
{
Some code...
}
ReSharper gab mir die obige Warnung ("if" -Anweisung umkehren, um die Verschachtelung zu reduzieren) und schlug die folgende Korrektur vor:
if (!some condition) return;
Some code...
Ich würde gerne verstehen, warum das besser ist. Ich habe immer gedacht, dass die Verwendung von "return" mitten in einer Methode problematisch ist, ähnlich wie "goto".
ASSERT( exampleParam > 0 )
.Antworten:
Eine Rückkehr in der Mitte der Methode ist nicht unbedingt schlecht. Es ist möglicherweise besser, sofort zurückzukehren, wenn dadurch die Absicht des Codes klarer wird. Beispielsweise:
In diesem Fall
_isDead
können wir , wenn dies zutrifft, die Methode sofort verlassen. Es könnte besser sein, es stattdessen so zu strukturieren:Ich habe diesen Code aus dem ausgewählt Refactoring-Katalog ausgewählt . Dieses spezielle Refactoring heißt: Ersetzen Sie verschachtelte Bedingungen durch Schutzklauseln.
quelle
Es ist nicht nur ästhetisch , sondern reduziert auch das maximale Verschachtelungsniveau innerhalb der Methode. Dies wird im Allgemeinen als ein Plus angesehen , weil es Methoden macht leichter zu verstehen (und in der Tat viele statischen Analyse - Tools bieten ein Maß dafür als einer der Indikatoren für die Qualität des Codes).
Auf der anderen Seite hat Ihre Methode auch mehrere Austrittspunkte, was eine andere Gruppe von Menschen für ein Nein-Nein hält.
Persönlich stimme ich ReSharper und der ersten Gruppe zu (in einer Sprache mit Ausnahmen finde ich es albern, "mehrere Austrittspunkte" zu diskutieren; fast alles kann werfen, daher gibt es in allen Methoden zahlreiche potenzielle Austrittspunkte).
In Bezug auf die Leistung : Beide Versionen sollten in jeder Sprache gleichwertig sein (wenn nicht auf IL-Ebene, dann sicher, nachdem der Jitter mit dem Code fertig ist). Theoretisch hängt dies vom Compiler ab, aber praktisch jeder heute weit verbreitete Compiler ist in der Lage, viel fortgeschrittenere Fälle der Codeoptimierung als diesen zu behandeln.
quelle
Dies ist ein religiöses Argument, aber ich stimme ReSharper zu, dass Sie weniger Verschachtelung bevorzugen sollten. Ich glaube, dass dies die Nachteile überwiegt, mehrere Rückwege von einer Funktion zu haben.
Der Hauptgrund für weniger Verschachtelung ist die Verbesserung der Lesbarkeit und Wartbarkeit des Codes . Denken Sie daran, dass viele andere Entwickler Ihren Code in Zukunft lesen müssen und Code mit weniger Einrückungen im Allgemeinen viel einfacher zu lesen ist.
Voraussetzungen sind ein gutes Beispiel dafür, wo es in Ordnung ist, zu Beginn der Funktion frühzeitig zurückzukehren. Warum sollte die Lesbarkeit des Restes der Funktion durch das Vorhandensein einer Vorbedingungsprüfung beeinträchtigt werden?
Was die Nachteile einer mehrfachen Rückgabe von einer Methode angeht: Debugger sind jetzt ziemlich leistungsfähig, und es ist sehr einfach herauszufinden, wo und wann eine bestimmte Funktion zurückkehrt.
Das Vorhandensein mehrerer Rückgaben in einer Funktion hat keine Auswirkungen auf den Job des Wartungsprogrammierers.
Schlechte Lesbarkeit des Codes wird.
quelle
Wie andere bereits erwähnt haben, sollte es keinen Leistungseinbruch geben, aber es gibt andere Überlegungen. Abgesehen von diesen berechtigten Bedenken kann dies unter bestimmten Umständen auch zu Fallstricken führen. Angenommen, Sie haben es
double
stattdessen mit einem zu tun :Vergleichen Sie das mit der scheinbar äquivalenten Umkehrung:
Unter bestimmten Umständen scheint es also
if
nicht so zu sein, was als korrekt invertiert erscheint .quelle
Die Idee, erst am Ende einer Funktion zurückzukehren, kam aus den Tagen, bevor Sprachen Ausnahmen unterstützten. Es ermöglichte Programmen, sich darauf zu verlassen, dass Bereinigungscode am Ende einer Methode eingefügt werden kann, und dann sicher zu sein, dass er aufgerufen wird und ein anderer Programmierer keine Rückgabe in der Methode verbirgt, die dazu führte, dass der Bereinigungscode übersprungen wurde . Übersprungener Bereinigungscode kann zu einem Speicher- oder Ressourcenverlust führen.
In einer Sprache, die Ausnahmen unterstützt, gibt es jedoch keine solchen Garantien. In einer Sprache, die Ausnahmen unterstützt, kann die Ausführung einer Anweisung oder eines Ausdrucks einen Kontrollfluss verursachen, der das Ende der Methode bewirkt. Dies bedeutet, dass die Bereinigung mithilfe der Schlüsselwörter finally oder mithilfe von Schlüsselwörtern erfolgen muss.
Wie auch immer, ich sage, ich denke, viele Leute zitieren die Richtlinie "Nur am Ende einer Methode zurückkehren", ohne zu verstehen, warum dies jemals eine gute Sache war, und dass das Reduzieren der Verschachtelung zur Verbesserung der Lesbarkeit wahrscheinlich ein besseres Ziel ist.
quelle
If you've got deep nesting, maybe your function is trying to do too many things.
=> Dies ist keine korrekte Konsequenz aus Ihrem vorherigen Satz. Denn kurz bevor Sie sagen, dass Sie Verhalten A mit Code C in Verhalten A mit Code D umgestalten können, ist Code D zwar sauberer, aber "zu viele Dinge" beziehen sich auf Verhalten, das sich NICHT geändert hat. Daher machen Sie mit dieser Schlussfolgerung keinen Sinn.Ich möchte hinzufügen, dass es einen Namen für die umgekehrten if's gibt - Guard-Klausel. Ich benutze es wann immer ich kann.
Ich hasse es, Code zu lesen, wo es am Anfang zwei Code-Bildschirme gibt und sonst nichts. Einfach umkehren, wenn und zurück. Auf diese Weise verschwendet niemand Zeit mit dem Scrollen.
http://c2.com/cgi/wiki?GuardClause
quelle
if
s oder nein enthält. lolDies wirkt sich nicht nur auf die Ästhetik aus, sondern verhindert auch das Verschachteln von Code.
Es kann tatsächlich als Voraussetzung dafür dienen, dass auch Ihre Daten gültig sind.
quelle
Das ist natürlich subjektiv, aber ich denke, es verbessert sich in zwei Punkten stark:
condition
hält.quelle
Mehrere Rückgabepunkte waren in C (und in geringerem Maße in C ++) ein Problem, da Sie gezwungen waren, den Bereinigungscode vor jedem der Rückgabepunkte zu duplizieren. Bei der Speicherbereinigung wird die
try
|finally
Konstrukt undusing
Blöcke, es gibt wirklich keinen Grund, warum Sie Angst vor ihnen haben sollten.Letztendlich kommt es darauf an, was Sie und Ihre Kollegen leichter lesen können.
quelle
a loop that is not vectorizable due to a second data-dependent exit
In Bezug auf die Leistung wird es keinen merklichen Unterschied zwischen den beiden Ansätzen geben.
Beim Codieren geht es jedoch um mehr als nur um Leistung. Klarheit und Wartbarkeit sind ebenfalls sehr wichtig. Und in solchen Fällen, in denen die Leistung nicht beeinträchtigt wird, ist dies das einzige, was zählt.
Es gibt konkurrierende Denkschulen, welcher Ansatz vorzuziehen ist.
Eine Ansicht ist die, die andere erwähnt haben: Der zweite Ansatz reduziert die Verschachtelungsebene, wodurch die Codeklarheit verbessert wird. Dies ist in einem imperativen Stil natürlich: Wenn Sie nichts mehr zu tun haben, können Sie genauso gut früh zurückkehren.
Eine andere Ansicht aus der Perspektive eines funktionaleren Stils ist, dass eine Methode nur einen Austrittspunkt haben sollte. Alles in einer funktionalen Sprache ist Ausdruck. Also, wenn Anweisungen immer eine else-Klausel haben müssen. Andernfalls hätte der if-Ausdruck nicht immer einen Wert. Im funktionalen Stil ist der erste Ansatz also natürlicher.
quelle
Schutzklauseln oder Vorbedingungen (wie Sie wahrscheinlich sehen können) prüfen, ob eine bestimmte Bedingung erfüllt ist, und unterbrechen dann den Programmfluss. Sie eignen sich hervorragend für Orte, an denen Sie wirklich nur an einem Ergebnis einer
if
Aussage interessiert sind . Also anstatt zu sagen:Sie kehren die Bedingung um und brechen, wenn diese umgekehrte Bedingung erfüllt ist
return
ist bei weitem nicht so schmutzig wiegoto
. Sie können einen Wert übergeben, um den Rest Ihres Codes anzuzeigen, den die Funktion nicht ausführen konnte.Sie sehen die besten Beispiele dafür, wo dies unter verschachtelten Bedingungen angewendet werden kann:
vs:
Es gibt nur wenige Leute, die argumentieren, das erste sei sauberer, aber natürlich völlig subjektiv. Einige Programmierer möchten gerne wissen, unter welchen Bedingungen etwas durch Einrücken arbeitet, während ich den Methodenfluss lieber linear halten möchte.
Ich werde nicht für einen Moment vorschlagen, dass Precons Ihr Leben verändern oder Sie zur Ruhe bringen, aber Sie finden Ihren Code möglicherweise ein bisschen einfacher zu lesen.
quelle
Hier werden mehrere gute Punkte gemacht, aber mehrere Rückgabepunkte können auch unlesbar sein , wenn die Methode sehr langwierig ist. Wenn Sie jedoch mehrere Rückgabepunkte verwenden möchten, stellen Sie sicher, dass Ihre Methode kurz ist. Andernfalls kann der Lesbarkeitsbonus mehrerer Rückgabepunkte verloren gehen.
quelle
Die Leistung besteht aus zwei Teilen. Sie haben Leistung, wenn die Software in Produktion ist, aber Sie möchten auch Leistung beim Entwickeln und Debuggen haben. Das Letzte, was ein Entwickler möchte, ist, auf etwas Triviales zu "warten". Am Ende führt das Kompilieren mit aktivierter Optimierung zu ähnlichem Code. Es ist also gut, diese kleinen Tricks zu kennen, die sich in beiden Szenarien auszahlen.
Der Fall in der Frage ist klar, ReSharper ist korrekt. Anstatt
if
Anweisungen zu verschachteln und einen neuen Bereich im Code zu erstellen, legen Sie zu Beginn Ihrer Methode eine klare Regel fest. Es erhöht die Lesbarkeit, ist einfacher zu warten und reduziert die Anzahl der Regeln, die durchgesehen werden müssen, um herauszufinden, wohin sie wollen.quelle
Persönlich bevorzuge ich nur 1 Ausstiegspunkt. Es ist einfach zu erreichen, wenn Sie Ihre Methoden kurz und auf den Punkt halten, und es bietet ein vorhersehbares Muster für die nächste Person, die an Ihrem Code arbeitet.
z.B.
Dies ist auch sehr nützlich, wenn Sie nur die Werte bestimmter lokaler Variablen innerhalb einer Funktion überprüfen möchten, bevor sie beendet wird. Alles, was Sie tun müssen, ist, einen Haltepunkt bei der endgültigen Rückkehr zu setzen, und Sie werden ihn garantiert erreichen (es sei denn, eine Ausnahme wird ausgelöst).
quelle
Viele gute Gründe, wie der Code aussieht . Aber was ist mit den Ergebnissen ?
Schauen wir uns einen C # -Code und seine IL-kompilierte Form an:
Dieses einfache Snippet kann kompiliert werden. Sie können die generierte EXE-Datei mit ildasm öffnen und überprüfen, was das Ergebnis ist. Ich werde nicht die ganze Assembler-Sache posten, aber ich werde die Ergebnisse beschreiben.
Der generierte IL-Code führt Folgendes aus:
Es scheint also, dass der Code bis zum Ende springen wird. Was ist, wenn wir ein normales mit verschachteltem Code ausführen?
Die Ergebnisse sind in IL-Anweisungen ziemlich ähnlich. Der Unterschied besteht darin, dass es vorher Sprünge pro Bedingung gab: Wenn false, gehe zum nächsten Code, wenn true, gehe zum Ende. Und jetzt fließt der IL-Code besser und hat 3 Sprünge (der Compiler hat dies ein wenig optimiert): 1. Erster Sprung: Wenn die Länge 0 ist, zu einem Teil, an dem der Code erneut springt (dritter Sprung) bis zum Ende. 2. Zweitens: In der Mitte der zweiten Bedingung, um eine Anweisung zu vermeiden. 3. Drittens: Wenn die zweite Bedingung falsch ist, springen Sie zum Ende.
Auf jeden Fall springt der Programmzähler immer.
quelle
Theoretisch
if
könnte das Invertieren zu einer besseren Leistung führen, wenn es die Trefferquote für die Verzweigungsvorhersage erhöht. In der Praxis ist es meiner Meinung nach sehr schwierig, genau zu wissen, wie sich die Verzweigungsvorhersage verhält, insbesondere nach dem Kompilieren. Daher würde ich dies in meiner täglichen Entwicklung nicht tun, außer wenn ich Assembler-Code schreibe.Mehr zur Verzweigungsvorhersage hier .
quelle
Das ist einfach umstritten. In der Frage der vorzeitigen Rückkehr besteht keine "Einigung zwischen Programmierern". Soweit ich weiß, ist es immer subjektiv.
Es ist möglich, ein Leistungsargument zu liefern, da es besser ist, Bedingungen zu haben, die so geschrieben sind, dass sie meistens wahr sind. Es kann auch argumentiert werden, dass es klarer ist. Auf der anderen Seite werden verschachtelte Tests erstellt.
Ich glaube nicht, dass Sie eine schlüssige Antwort auf diese Frage bekommen werden.
quelle
Es gibt dort bereits viele aufschlussreiche Antworten, aber dennoch möchte ich auf eine etwas andere Situation hinweisen: Anstelle einer Vorbedingung, die tatsächlich über eine Funktion gestellt werden sollte, denken Sie an eine schrittweise Initialisierung, bei der Sie Sie müssen nach jedem Schritt suchen, um erfolgreich zu sein, und dann mit dem nächsten fortfahren. In diesem Fall können Sie nicht alles oben überprüfen.
Ich fand meinen Code beim Schreiben einer ASIO-Host-Anwendung mit Steinbergs ASIOSDK wirklich unlesbar, da ich dem Verschachtelungsparadigma folgte. Es war ungefähr acht Ebenen tief und ich kann dort keinen Designfehler sehen, wie oben von Andrew Bullock erwähnt. Natürlich hätte ich inneren Code in eine andere Funktion packen und dann die verbleibenden Ebenen dort verschachteln können, um sie besser lesbar zu machen, aber das scheint mir eher zufällig zu sein.
Indem ich die Verschachtelung durch Schutzklauseln ersetzte, entdeckte ich sogar ein Missverständnis von mir in Bezug auf einen Teil des Bereinigungscodes, der innerhalb der Funktion viel früher als am Ende hätte auftreten sollen. Bei verschachtelten Zweigen hätte ich das nie gesehen, man könnte sogar sagen, dass sie zu meinem Missverständnis geführt haben.
Dies könnte also eine andere Situation sein, in der invertierte Wenns zu einem klareren Code beitragen können.
quelle
Das Vermeiden mehrerer Austrittspunkte kann zu Leistungssteigerungen führen. Ich bin mir bei C # nicht sicher, aber in C ++ hängt die Optimierung des benannten Rückgabewerts (Copy Elision, ISO C ++ '03 12.8 / 15) von einem einzigen Exit-Punkt ab. Durch diese Optimierung wird vermieden, dass Ihr Rückgabewert durch Kopieren erstellt wird (in Ihrem speziellen Beispiel spielt dies keine Rolle). Dies kann zu erheblichen Leistungssteigerungen in engen Schleifen führen, da Sie bei jedem Aufruf der Funktion einen Konstruktor und einen Destruktor speichern.
In 99% der Fälle ist das Speichern der zusätzlichen Konstruktor- und Destruktoraufrufe jedoch nicht den Verlust der Lesbarkeit wert, den verschachtelte
if
Blöcke verursachen (wie andere bereits betont haben).quelle
Es ist Ansichtssache.
Mein normaler Ansatz wäre es, einzeilige Wenns zu vermeiden und mitten in einer Methode zurückzukehren.
Sie möchten keine Zeilen, wie sie überall in Ihrer Methode vorgeschlagen werden, aber es gibt etwas zu sagen, um eine Reihe von Annahmen oben in Ihrer Methode zu überprüfen und Ihre eigentliche Arbeit nur dann zu erledigen, wenn alle erfolgreich sind.
quelle
Meiner Meinung nach ist eine frühzeitige Rückgabe in Ordnung, wenn Sie nur void zurückgeben (oder einen nutzlosen Rückgabecode, den Sie nie überprüfen werden), und dies könnte die Lesbarkeit verbessern, da Sie das Verschachteln vermeiden und gleichzeitig explizit angeben, dass Ihre Funktion ausgeführt ist.
Wenn Sie tatsächlich einen returnValue zurückgeben, ist die Verschachtelung normalerweise der bessere Weg, da Sie Ihren returnValue nur an einer Stelle (am Ende - duh) zurückgeben und Ihr Code in vielen Fällen möglicherweise besser gewartet werden kann.
quelle
Ich bin mir nicht sicher, aber ich denke, dass R # versucht, Weitsprünge zu vermeiden. Wenn Sie IF-ELSE haben, führt der Compiler Folgendes aus:
Bedingung false -> Weitsprung zu false_condition_label
true_condition_label: Anweisung1 ... Anweisung_n
false_condition_label: Anweisung1 ... Anweisung_n
Endblock
Wenn die Bedingung wahr ist, gibt es keinen Sprung und keinen Rollout-L1-Cache, aber der Sprung zu false_condition_label kann sehr weit sein und der Prozessor muss seinen eigenen Cache rollen. Das Synchronisieren des Cache ist teuer. R # -Versuche ersetzen Weitsprünge in Kurzsprünge und in diesem Fall besteht eine größere Wahrscheinlichkeit, dass sich alle Anweisungen bereits im Cache befinden.
quelle
Ich denke, es hängt davon ab, was Sie bevorzugen, wie erwähnt, es gibt keine allgemeine Übereinstimmung afaik. Um die Belästigung zu verringern, können Sie diese Art von Warnung auf "Hinweis" reduzieren.
quelle
Meine Idee ist, dass die Rückkehr "mitten in einer Funktion" nicht so "subjektiv" sein sollte. Der Grund ist ganz einfach, nehmen Sie diesen Code:
Vielleicht ist die erste "Rückkehr" nicht so intuitiv, aber das spart wirklich. Es gibt zu viele "Ideen" zu sauberen Codes, die einfach mehr Übung benötigen, um ihre "subjektiven" schlechten Ideen zu verlieren.
quelle
Diese Art der Codierung bietet mehrere Vorteile, aber für mich ist der große Gewinn: Wenn Sie schnell zurückkehren können, können Sie die Geschwindigkeit Ihrer Anwendung verbessern. IE Ich weiß, dass ich aufgrund der Voraussetzung X schnell mit einem Fehler zurückkehren kann. Dadurch werden zuerst die Fehlerfälle beseitigt und die Komplexität Ihres Codes verringert. In vielen Fällen können Pipeline-Abstürze oder -Schalter gestoppt werden, da die CPU-Pipeline jetzt sauberer sein kann. Zweitens, wenn Sie sich in einer Schleife befinden, können Sie durch schnelles Brechen oder Zurückkehren viel CPU sparen. Einige Programmierer verwenden Schleifeninvarianten, um diese Art von schnellem Beenden durchzuführen. In diesem Fall können Sie jedoch Ihre CPU-Pipeline unterbrechen und sogar Probleme mit der Speichersuche verursachen. Dies bedeutet, dass die CPU aus dem externen Cache geladen werden muss. Aber im Grunde denke ich, du solltest tun, was du beabsichtigt hast, Das heißt, das Ende der Schleife oder Funktion erstellt keinen komplexen Codepfad, nur um eine abstrakte Vorstellung von korrektem Code zu implementieren. Wenn das einzige Werkzeug, das Sie haben, ein Hammer ist, sieht alles aus wie ein Nagel.
quelle