Teilen Sie die Berechnung des Rückgabewerts und die return-Anweisung in einzeilige Methoden?

26

Ich hatte eine Diskussion mit einem Kollegen über das Brechen einer returnAnweisung und die Anweisung, die den Rückgabewert in zwei Zeilen berechnet.

Beispielsweise

private string GetFormattedValue()
{
    var formattedString = format != null ? string.Format(format, value) : value.ToString();
    return formattedString;
}

anstatt

private string GetFormattedValue()
{
    return format != null ? string.Format(format, value) : value.ToString();
}

Was den Code betrifft, sehe ich in der ersten Variante keinen wirklichen Wert. Für mich ist letzteres klarer, insbesondere für Methoden, die so kurz sind. Sein Argument war, dass die frühere Variante einfacher zu debuggen ist - was ein kleiner Vorteil ist, da VisualStudio eine sehr detaillierte Überprüfung der Anweisungen ermöglicht, wenn die Ausführung aufgrund eines Haltepunkts gestoppt wird.

Meine Frage ist, ob es immer noch sinnvoll ist, weniger klaren Code zu schreiben, um das Debuggen zu vereinfachen. Gibt es weitere Argumente für die Variante mit der aufgeteilten Berechnung und returnAnweisung?

Paul Kertscher
quelle
18
Funktioniert nicht mit VS, geht aber davon aus, dass Sie für einen komplizierten Ausdruck keinen bedingten Haltepunkt festlegen können (oder dass die Eingabe kompliziert ist), und würde daher wahrscheinlich die Zuweisung und Rückgabe aus praktischen Gründen in separate Anweisungen schreiben. Der Compiler würde sehr wahrscheinlich sowieso für beide den gleichen Code finden.
Tofro
1
Dies ist möglicherweise sprachabhängig, insbesondere in Sprachen, in denen Variablen (möglicherweise komplexe) Objektverhalten anstelle von Zeigerverhalten aufweisen. Die Aussage von @Paul K gilt wahrscheinlich für Sprachen mit Zeigerverhalten, für Sprachen, in denen Objekte ein einfaches Werteverhalten aufweisen, und für Sprachen mit ausgereiften, hochwertigen Compilern.
MSalters
4
"Da VisualStudio uns eine sehr detaillierte Prüfung der Anweisungen ermöglicht, wenn die Ausführung aufgrund eines Haltepunkts gestoppt wird" - ist das so. Wie erhält man den Rückgabewert, wenn die Funktion eine Struktur mit mehr als einem Member zurückgibt? (und die Unterstützung für diese Funktion ist bestenfalls lückenhaft. Es gibt viele Kombinationen, bei denen der Rückgabewert überhaupt nicht angezeigt wird.)
Voo
2
Wie macht es die "sehr detaillierte Prüfung der Anweisungen" im Debugger, die jemand HEUTE verwendet, zu einer schlechten Option, den Code zu schreiben, damit das Debuggen in JEDEM Debugger einfach ist?
Ian
16
Ärgern Sie ihn weiter, indem Sie den gesamten Funktionskörper aufprivate string GetFormattedValue() => string.Format(format ?? "{0}", value);
Graham

Antworten:

46

Das Einführen von erklärenden Variablen ist ein bekanntes Refactoring, das manchmal dazu beiträgt, komplizierte Ausdrücke besser lesbar zu machen. Im gezeigten Fall jedoch

  • Die zusätzliche Variable "erklärt" nichts, was nicht aus dem umgebenden Methodennamen hervorgeht
  • die aussage wird noch länger, also (etwas) weniger lesbar

Darüber hinaus können neuere Versionen des Visual Studio - Debugger den Rückgabewert einer Funktion in den meisten Fällen zeigen , ohne eine überflüssige Variable einzuführen (aber Vorsicht, es gibt einige Einschränkungen, einen Blick auf haben diese ältere SO Post und die verschiedenen Antworten ).

In diesem speziellen Fall stimme ich Ihnen jedoch zu, dass eine erklärende Variable in der Tat die Codequalität verbessern kann.

Doc Brown
quelle
Ich bin damit einverstanden, auch, dass es auf jeden Fall sind Fälle , in denen es sinnvoll, kein Zweifel.
Paul Kertscher
2
Ich benutze normalerweise resultals Name der Variablen. Nicht mehr so ​​viel länger und einfacher zu debuggen
edc65
26
@ edc65: Ein generischer Name wie er result fügt dem Code oft nur Rauschen hinzu und erhöht selten die Lesbarkeit, was genau der Punkt ist, auf den ich antworte. Dies kann in einem Kontext gerechtfertigt sein, in dem es beim Debuggen hilft, aber ich würde es vermeiden, wenn ich einen Debugger verwende, der keine separate Variable benötigt.
Doc Brown
6
@ JonHanna Weg Werkzeug lange nach mir. Der Name resultvermittelt die Information, dass dies der Wert ist, der sich aus der Funktion ergibt, so dass Sie ihn ansehen können, bevor die Funktion zurückkehrt.
EDC65
1
@ edc65 aber das macht es nützlich. Wenn ich jetzt Ihren Code lese, wird mir nicht sofort klar, dass dies nicht der Fall ist. So ist Ihr Code weniger lesbar geworden.
Jon Hanna
38

Angesichts der Tatsachen, dass:

a) Es gibt keine Auswirkung auf den endgültigen Code, da der Compiler die Variable entfernt.

b) Wenn es separat ist, wird die Debugging-Fähigkeit verbessert.

Ich persönlich bin zu dem Schluss gekommen, dass es eine gute Praxis ist, sie 99% der Zeit zu trennen.

Dies hat keine wesentlichen Nachteile. Das Argument, dass Code aufgebläht wird, ist eine falsche Bezeichnung, da aufgeblähter Code im Vergleich zu unlesbarem oder schwer zu debuggendem Code ein triviales Problem darstellt. Darüber hinaus kann diese Methode selbst keinen verwirrenden Code erstellen, was ganz dem Entwickler überlassen bleibt.

Dom
quelle
9
Das ist die richtige Antwort für mich. Dies erleichtert das Festlegen eines Haltepunkts und das Anzeigen des Werts beim Debuggen und hat keine Nachteile, die mir bekannt sind.
Matthew James Briggs
Setzen Sie für Punkt b in Visual Studio Code einfach einen Haltepunkt in die Rückgabe und fügen Sie den folgenden Ausdruck hinzu: GetFormattedValue (). Dadurch wird das Ergebnis angezeigt, wenn der Haltepunkt erreicht wird, sodass die zusätzliche Zeile nicht benötigt wird. Es ist jedoch einfacher, die Einheimischen mit einer zusätzlichen Zeile zu sehen, da keine zusätzlichen Ausdrücke im Debugger hinzugefügt werden müssen. Also wirklich eine Frage der persönlichen Präferenz.
Jon Raynor
3
Setzen Sie für den Rückgabewert @JonRaynor den Haltepunkt in die schließende Klammer der Funktion. Es fängt den zurückgegebenen Wert ab, auch in Funktionen mit mehreren Rückgaben.
Baldrickk
16

Oft ist es sehr hilfreich, eine Variable einzuführen, um nur ein Ergebnis zu benennen, wenn der Code selbstdokumentierender wird. In diesem Fall ist dies kein Faktor, da der Variablenname dem Methodennamen sehr ähnlich ist.

Beachten Sie, dass einzeilige Methoden keinen inhärenten Wert haben. Wenn eine Änderung mehr Zeilen einführt, aber den Code klarer macht, ist dies eine gute Änderung.

Im Allgemeinen hängen diese Entscheidungen jedoch stark von Ihren persönlichen Vorlieben ab. ZB finde ich beide Lösungen verwirrend, weil der bedingte Operator unnötig verwendet wird. Ich hätte eine if-Anweisung vorgezogen. In Ihrem Team haben Sie jedoch möglicherweise andere Konventionen vereinbart. Tun Sie dann, was Ihre Konventionen andeuten. Wenn die Konventionen zu einem solchen Fall keine Aussage machen, beachten Sie, dass dies eine äußerst geringfügige Änderung ist, die auf lange Sicht keine Rolle spielt. Wenn dieses Muster wiederholt auftritt, leiten Sie möglicherweise eine Diskussion darüber ein, wie Sie als Team diese Fälle behandeln möchten. Aber das spaltet die Haare zwischen "gutem Code" und "vielleicht ein bisschen besserem Code".

amon
quelle
1
"Ich finde beide Lösungen verwirrend, weil der bedingte Operator unnötig verwendet wird." - Es ist kein Beispiel aus der realen Welt, ich musste nur schnell etwas erfinden. Zugegebenermaßen ist dies möglicherweise nicht das beste Beispiel.
Paul Kertscher
4
+1, wenn man im Wesentlichen sagt, dass dies ein "under the radar" -Unterschied ist, bei dem es sich nicht lohnt, sich darum zu kümmern, wenn andere Dinge gleich sind.
TripeHound
3
@Mindwin, wenn ich den ternären Operator verwende, teile ich ihn normalerweise in mehrere Zeilen auf, damit klar ist, was der wahre und was der falsche Fall ist.
Arturo Torres Sánchez
2
@ ArturoTorresSánchez Das mache ich auch, aber anstelle von a ?und a :benutze ich if() {und } else {- - - - \\ :)
Mindwin
3
@ Mindwin, aber ich kann das nicht tun, wenn ich mitten in einem Ausdruck bin (wie ein Objektinitialisierer)
Arturo Torres Sánchez
2

Als Antwort auf Ihre Fragen:

Meine Frage ist, ob es immer noch sinnvoll ist, weniger klaren Code zu schreiben, um das Debuggen zu vereinfachen.

Ja. In der Tat, einen Teil Ihrer früheren Aussage scheint mir (keine Beleidigung) ein zu wenig kurzsichtig (siehe fett unten) " Sein Argument auch immer war , dass die frühere Variante einfacher zu debuggen ist - was ziemlich ein kleines Verdienst ist , da Visual Studio ermöglicht uns eine sehr detaillierte Einsichtnahme in die Anweisungen, wenn die Ausführung aufgrund einer Unterbrechungsstelle gestoppt wird. "

Das Debuggen zu vereinfachen ist (fast) nie von " geringem Wert", da nach Schätzungen 50% der Zeit eines Programmierers für das Debuggen aufgewendet wird ( Reversible Debugging Software ).

Gibt es weitere Argumente für die Variante mit der Splitberechnung und der Return-Anweisung?

Ja. Einige Entwickler würden argumentieren, dass die Aufteilungsberechnung einfacher zu lesen ist. Dies hilft natürlich beim Debuggen, hilft aber auch, wenn jemand versucht, Geschäftsregeln zu entschlüsseln, die Ihr Code möglicherweise ausführt oder anwendet.

ANMERKUNG: Geschäftsregeln können in einer Datenbank möglicherweise besser bereitgestellt werden, da sie sich häufig ändern können. Dennoch ist eine eindeutige Kodierung in diesem Bereich nach wie vor von größter Bedeutung. ( So erstellen Sie eine Business Rules Engine )

tale852150
quelle
1

Ich würde noch weiter gehen:

private string GetFormattedValue()
{
    if (format != null) {
        formattedString = string.Format(format, value);
    } else {
        formattedString = value.ToString()
    }
    return formattedString;
}

Warum?

Die Verwendung von ternären Operatoren für komplexere Logik wäre nicht lesbar. Daher würden Sie einen Stil wie den oben genannten für komplexere Anweisungen verwenden. Indem Sie diesen Stil immer verwenden, ist Ihr Code konsistent und für einen Menschen einfacher zu analysieren. Durch die Einführung dieser Art von Konsistenz (und die Verwendung von Code-Lints und anderen Tests) können Sie außerdem goto failTippfehler vermeiden .

Ein weiterer Vorteil ist, dass Sie in Ihrem Bericht zur Codeabdeckung darüber informiert werden, wenn Sie vergessen haben, einen Test für formatnicht null anzugeben. Dies wäre beim ternären Operator nicht der Fall.


Meine bevorzugte Alternative - wenn Sie in der Gruppe "So schnell wie möglich eine Rendite erzielen" sind und nicht gegen Mehrfachrenditen einer Methode:

private string GetFormattedValue()
{
    if (format != null) {
        return string.Format(format, value);
    }

    return value.ToString();
}

Sie können sich also die letzte Rückgabe ansehen, um die Standardeinstellung zu ermitteln.

Es ist jedoch wichtig, konsistent zu sein und alle Ihre Methoden der einen oder anderen Konvention zu folgen.

HorusKol
quelle
1
Das erste Beispiel scheint eine schlechte Übung zu sein, da es value.ToString()unnötig aufgerufen wird, wenn das Format nicht null ist. Im Allgemeinen kann dies nicht-triviale Berechnungen umfassen und länger dauern als die Version, die eine Formatzeichenfolge enthält. Angenommen, a valuespeichert PI mit einer Million Dezimalstellen und eine Formatzeichenfolge, die nur die ersten Ziffern anfordert.
Steve
1
Warum nicht private string GetFormattedValue() => string.Format(format ?? "{0}", value); Gleicher Effekt? Verwenden Sie Unit-Tests, um die Korrektheit sicherzustellen, anstatt sich auf den Debugger zu verlassen.
Berin Loritsch
1
Während ich ein ternäres zustimmen kann weniger klar sein, kann der Nullabschluss Dinge machen mehr klar. Zumindest in diesem Fall.
Berin Loritsch
1
Liebes Tagebuch, heute habe ich gelesen, dass das Schreiben von klarem und prägnantem Code unter Verwendung bekannter (seit etwa 40 Jahren bestehender) Paradigmen, Redewendungen und Operatoren Zitat, doppeltes Zitat ist, gescheites doppeltes Zitat, nicht Zitat - aber stattdessen das Schreiben von übermäßig ausführlichem Code, der gegen Code verstößt DRY und nicht die Verwendung der oben genannten Operatoren, Redewendungen und Paradigmen, während stattdessen versucht wird, alles zu vermeiden, was möglicherweise nur für einen Fünfjährigen ohne Programmierhintergrund kryptisch erscheint - ist stattdessen Klarheit . Verdammt, ich muss echt alt geworden sein, mein liebes Tagebuch ... Ich hätte Go lernen sollen, als ich die Chance dazu hatte.
Vaxquis
1
"Ternäre Operatoren für komplexere Logik zu verwenden, wäre unlesbar." Obwohl dies tatsächlich der Fall ist (und ich habe gesehen, dass die Leute die Logik überkomplizieren), trifft dies nicht auf den OP-Code zu und ist auch für ternäre Operatoren nicht spezifisch. Das einzige, was ich mit voller Zuversicht sagen kann, ist, dass die Leitung zu lang ist. gist.github.com/milleniumbug/cf9b62cac32a07899378feef6c06c776 ist, wie ich es neu formatieren würde.
Milleniumbug
1

Ich denke nicht, dass eine solche Technik durch die Notwendigkeit des Debuggens gerechtfertigt werden kann. Ich bin selbst tausendmal auf diesen Ansatz gestoßen und mache das von Zeit zu Zeit, aber ich denke immer daran, was Martin Fowler über das Debuggen gesagt hat :

Die Leute unterschätzen auch die Zeit, die sie mit dem Debuggen verbringen. Sie unterschätzen, wie viel Zeit sie damit verbringen können, einem langen Käfer nachzujagen. Beim Testen weiß ich sofort, wann ich einen Fehler hinzugefügt habe. So kann ich den Fehler sofort beheben, bevor er sich verstecken kann. Es gibt nur wenige Dinge, die frustrierender oder zeitraubender sind als das Debuggen. Wäre es nicht verdammt viel schneller, wenn wir die Bugs überhaupt nicht hätten?

Zapadlo
quelle
Martin Fowler ist ein intelligenter Mann, und ich habe es genossen, seine (und Ihre) Ansichten zu lesen. Während ich fest davon überzeugt bin, dass Tests notwendig sind und dass mehr Zeit für diese Bemühungen aufgewendet werden sollte, legt die Tatsache, dass wir alle fehlbare Menschen sind, nahe, dass keine Menge an Tests alle Bugs auslöschen wird . Daher wird das Debuggen immer Teil des Programmentwicklungs- und Supportprozesses sein.
Tale852150
1

Ich denke, einige Leute, wie der Ternäroperator, sind in Fragen verstrickt, die die Frage berühren. Ja, viele Leute hassen es, also ist es vielleicht gut, es trotzdem zu erwähnen.

In Bezug auf den Fokus Ihrer Frage wird die zurückgegebene Anweisung so verschoben, dass sie von einer Variablen referenziert wird ...

Diese Frage geht von zwei Annahmen aus, mit denen ich nicht einverstanden bin:

  1. Dass die zweite Variante klarer oder leichter zu lesen ist (ich sage das Gegenteil ist wahr), und

  2. dass jeder Visual Studio verwendet. Ich habe Visual Studio oft verwendet und kann es problemlos verwenden, aber ich verwende normalerweise etwas anderes. Eine Entwicklungsumgebung, die eine bestimmte IDE erzwingt, ist eine, der ich skeptisch gegenüber sein würde.

Das Aufteilen auf eine benannte Variable erschwert selten das Lesen, fast immer das Gegenteil. Die spezifische Art und Weise, in der jemand dies tut, könnte Probleme verursachen, beispielsweise wenn ein Overlord der Selbstdokumentation dies tut, var thisVariableIsTheFormattedResultAndWillBeTheReturnValue = ...dann ist dies offensichtlich schlecht, aber dies ist ein separates Problem. var formattedText = ...ist gut.

In diesem speziellen Fall und möglicherweise in vielen Fällen, da es sich um Einzeiler handelt, sagt Ihnen die Variable nicht viel, was der Funktionsname noch nicht sagt. Daher fügt die Variable nicht so viel hinzu. Das Debugging-Argument könnte immer noch stimmen, aber auch in diesem speziellen Fall sehe ich nichts, worauf Sie sich beim Debuggen konzentrieren könnten, und es kann später jederzeit leicht geändert werden, wenn jemand das Format für das Debuggen oder etwas anderes benötigt.

Im Allgemeinen, und Sie haben nach der allgemeinen Regel gefragt (Ihr Beispiel war nur das, ein Beispiel für eine verallgemeinerte Form), sind alle Punkte, die für Variante 1 (2-Liner) gemacht wurden, korrekt. Das sind gute Richtlinien. Leitlinien müssen jedoch flexibel sein. Zum Beispiel hat das Projekt, an dem ich gerade arbeite, ein Maximum von 80 Zeichen pro Zeile, also teile ich viele Zeilen auf, aber ich finde normalerweise Zeilen von 81 bis 85 Zeichen, die sich nur umständlich aufteilen oder die Lesbarkeit verringern lassen, und ich lasse sie übrig das Limit.

Da es unwahrscheinlich ist, dass ein Mehrwert erzielt wird, würde ich für das angegebene Beispiel keine zwei Zeilen schreiben. Ich würde Variante 2 (den 1-Liner) machen, weil die Punkte nicht stark genug sind, um in diesem Fall etwas anderes zu machen.

Aaron
quelle