Temporäre Variablen im Vergleich zu den Anforderungen an die Zeilenlänge

10

Ich habe Martin Fowlers Refactoring gelesen . Es ist im Allgemeinen ausgezeichnet, aber eine von Fowlers Empfehlungen scheint ein wenig Ärger zu verursachen.

Fowler empfiehlt, temporäre Variablen durch eine Abfrage zu ersetzen. Stattdessen:

double getPrice() {
    final int basePrice = _quantity * _itemPrice;
    final double discountFactor;
    if (basePrice > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice * discountFactor;
}

Sie ziehen sich in eine Hilfsmethode zurück:

double basePrice() {
    return _quantity * _itemPrice;
}

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice() * discountFactor;
}

Im Allgemeinen stimme ich zu, außer dass ein Grund, warum ich temporäre Variablen verwende, darin besteht, dass eine Zeile zu lang ist. Zum Beispiel:

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

Wenn ich das einfügen würde, würde die Zeile länger als 80 Zeichen sein.

Alternativ habe ich Codeketten, die selbst nicht viel einfacher zu lesen sind:

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

Welche Strategien gibt es, um die beiden in Einklang zu bringen?

Kevin Burke
quelle
10
80 Zeichen sind ungefähr 1/3 von 1 meiner Monitore. Sind Sie sicher, dass es sich für Sie immer noch lohnt, sich an 80 Zeichen zu halten?
jk.
10
Ja, siehe zum Beispiel programmers.stackexchange.com/questions/604/…
Kevin Burke
Ihr $hostund Ihr $uriBeispiel ist jedoch irgendwie erfunden - es sei denn, der Host wurde aus einer Einstellung oder einer anderen Eingabe gelesen, ich würde es vorziehen, wenn sie sich in derselben Zeile befinden, selbst wenn sie umbrochen werden oder vom Rand abweichen.
Izkata
5
Keine Notwendigkeit, so dogmatisch zu sein. Das Buch ist eine Liste von Techniken, die verwendet werden können, wenn sie helfen, und nicht eine Reihe von Regeln, die Sie jedes Mal überall anwenden müssen. Der Punkt ist, Ihren Code wartbarer und leichter lesbar zu machen. Wenn ein Refactor dies nicht tut, verwenden Sie es nicht.
Sean McSomething
Während ich denke, dass ein Limit von 80 Zeichen etwas zu hoch ist, ist ein ähnliches Limit (100?) Vernünftig. Zum Beispiel programmiere ich oft gerne auf hochformatigen Monitoren, daher können extra lange Zeilen ärgerlich sein (zumindest wenn sie häufig sind).
Thomas Eding

Antworten:

16

Gewusst wie:
1. Es gibt Einschränkungen hinsichtlich der Zeilenlänge, damit Sie mehr Code sehen und verstehen können. Sie sind noch gültig.
2. Betonen Sie das Urteil über blinde Konventionen .
3. Vermeiden Sie temporäre Variablen, es sei denn, Sie optimieren die Leistung .
4. Vermeiden Sie die Verwendung tiefer Einrückungen für die Ausrichtung in mehrzeiligen Anweisungen.
Lange Anweisungen in mehrere Zeilen entlang 5. Pause Idee Grenzen :

// prefer this
var distance = Math.Sqrt(
    Math.Pow(point2.GetX() - point1.GetX(), 2) + // x's
    Math.Pow(point2.GetY() - point1.GetY(), 2)   // y's
);

// over this
var distance = Math.Sqrt(Math.Pow(point2.GetX() -
    point1.GetX(), 2) + Math.Pow(point2.GetY() -
    point1.GetY(), 2)); // not even sure if I typed that correctly.

Begründung
Die Hauptursache für meine (Debugging-) Probleme mit temporären Variablen ist, dass sie in der Regel veränderlich sind. Ich gehe nämlich davon aus, dass es sich beim Schreiben des Codes um einen Wert handelt. Wenn die Funktion jedoch komplex ist, ändert ein anderer Code ihren Status zur Hälfte. (Oder umgekehrt, wo der Status der Variablen gleich bleibt, sich aber das Ergebnis der Abfrage geändert hat).

Halten Sie sich an Abfragen, es sei denn, Sie optimieren die Leistung . Dadurch bleibt jede Logik, die Sie zur Berechnung dieses Werts verwendet haben, an einem einzigen Ort erhalten.

Die von Ihnen angegebenen Beispiele (Java und ... PHP?) Ermöglichen mehrzeilige Anweisungen. Wenn die Linien lang werden, brechen Sie sie auf. Die jquery-Quelle bringt dies auf die Spitze . (Die erste Anweisung führt zu Zeile 69!) Nicht, dass ich dem unbedingt zustimmen müsste, aber es gibt andere Möglichkeiten, Ihren Code lesbar zu machen, als temporäre Variablen zu verwenden.

Einige Beispiele
1. PEP 8 Style Guide für Python (nicht das schönste Beispiel)
2. Paul M Jones im Pear Style Guide (Argument in der Mitte der Straße)
3. Oracle-Zeilenlänge + Wrapping-Konventionen (nützliche Strategien zur Einhaltung von 80 Zeichen)
4. MDN-Java-Praktiken (betont das Urteil des Programmierers über die Konvention)

Zachary Yates
quelle
1
Der andere Teil des Problems besteht darin, dass eine temporäre Variable häufig ihren Wert überlebt. Kein Problem bei kleinen Oszilloskopblöcken, aber bei größeren, ja, ein großes Problem.
Ross Patterson
8
Wenn Sie sich Sorgen machen, dass das Temporär geändert wird, setzen Sie eine Konstante darauf.
Thomas Eding
3

Ich denke, das beste Argument für die Verwendung von Hilfsmethoden anstelle von temporären Variablen ist die menschliche Lesbarkeit. Wenn Sie als Mensch mehr Probleme beim Lesen der Helfer-Methodenkette haben als bei der temporären Varialbe, sehe ich keinen Grund, warum Sie sie extrahieren sollten.

(Bitte korrigiere mich wenn ich falsch liege)

mhr
quelle
3

Ich denke nicht, dass Sie die Richtlinien für 80 Zeichen genau befolgen müssen oder dass jemals eine lokale temporäre Variable extrahiert werden sollte. Es sollten jedoch lange Schlangen und lokale Temperaturen untersucht werden, um die gleiche Idee besser auszudrücken. Grundsätzlich weisen sie darauf hin, dass eine bestimmte Funktion oder Zeile zu kompliziert ist und wir sie zerlegen müssen. Aber wir müssen vorsichtig sein, denn eine schlechte Aufteilung einer Aufgabe macht die Situation nur noch komplizierter. Also soll ich die Dinge in wiederverwendbare und einfache Komponenten zerlegen.

Lassen Sie mich die Beispiele betrachten, die Sie veröffentlicht haben.

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

Meine Beobachtung ist, dass alle Twilio-API-Aufrufe mit "https://api.twilio.com/2010-04-1/" beginnen und daher eine sehr offensichtliche wiederverwendbare Funktion vorhanden ist:

$uri = twilioURL("Accounts/$accountSid/Usage/Records/AllTime")

Tatsächlich denke ich, dass der einzige Grund, eine URL zu generieren, darin besteht, die Anfrage zu stellen, also würde ich Folgendes tun:

$response = TwilioApi::makeRequest("Accounts/$accountSid/Usage/Records/AllTime")

Tatsächlich beginnen viele der URLs mit "Accounts / $ accountSid", daher würde ich das wahrscheinlich auch extrahieren:

$response = TwilioApi::makeAccountRequest($accountSid, "Usage/Records/AllTime")

Und wenn wir die Twilio-API zu einem Objekt machen, das die Kontonummer enthält, können wir Folgendes tun:

$response = $twilio->makeAccountRequest("Usage/Records/AllTime")

Die Verwendung eines $ twilio-Objekts hat den Vorteil, dass Unit-Tests einfacher werden. Ich kann dem Objekt ein anderes $ twilio-Objekt geben, das Twilio nicht zurückruft, was schneller ist und Twilio keine seltsamen Dinge antut.

Schauen wir uns den anderen an

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

Hier würde ich entweder darüber nachdenken:

$params = MustacheOptions::buildFromParams($bagcheck->getParams());

oder

$params = MustacheOptions::build($bagcheck->getFlatParams());

oder

$params = MustacheOptions::build(flatParams($backCheck));

Je nachdem, welche Sprache wiederverwendbarer ist.

Winston Ewert
quelle
1

Eigentlich bin ich mit dem angesehenen Mr. Fowler im allgemeinen Fall nicht einverstanden.

Der Vorteil des Extrahierens einer Methode aus früher inlineem Code ist die Wiederverwendung von Code. Der Code in der Methode ist jetzt von seiner ursprünglichen Verwendung getrennt und kann jetzt an anderen Stellen im Code verwendet werden, ohne kopiert und eingefügt zu werden (was Änderungen an mehreren Stellen erforderlich machen würde, wenn sich die allgemeine Logik des kopierten Codes jemals ändern müsste). .

Von gleichem, oft größerem konzeptionellen Wert ist jedoch die "Wiederverwendung von Werten". Mr. Fowler nennt diese extrahierten Methoden, um temporäre Variablen zu ersetzen, "Abfragen". Was ist effizienter? eine Datenbank mehrmals abfragen, wenn Sie einen bestimmten Wert benötigen, oder einmal abfragen und das Ergebnis speichern (vorausgesetzt, der Wert ist statisch genug, dass Sie nicht erwarten würden, dass er sich ändert)?

Für fast jede Berechnung, die über die relativ triviale in Ihrem Beispiel hinausgeht, ist es in den meisten Sprachen billiger, das Ergebnis einer Berechnung zu speichern, als es weiter zu berechnen. Daher ist die allgemeine Empfehlung, On-Demand neu zu berechnen, unaufrichtig. Es kostet mehr Entwicklerzeit und mehr CPU-Zeit und spart eine unbedeutende Menge an Speicher, was in den meisten modernen Systemen die billigste Ressource dieser drei ist.

Jetzt könnte die Hilfsmethode in Verbindung mit anderem Code "faul" gemacht werden. Beim ersten Ausführen wird eine Variable initialisiert. Alle weiteren Aufrufe würden diese Variable zurückgeben, bis die Methode explizit angewiesen wurde, neu zu berechnen. Dies kann ein Parameter für die Methode sein oder ein Flag, das von einem anderen Code gesetzt wird und den Wert ändert, von dem die Berechnung dieser Methode abhängt:

double? _basePrice; //not sure if Java has C#'s "nullable" concept
double basePrice(bool forceCalc)
{
   if(forceCalc || !_basePrice.HasValue)
      return _basePrice = _quantity * _itemPrice;
   return _basePrice.Value;
}

Für diese triviale Berechnung wird sogar noch mehr Arbeit geleistet als gespeichert. Daher würde ich generell empfehlen, bei der temporären Variablen zu bleiben. Für komplexere Berechnungen, die Sie im Allgemeinen vermeiden möchten, mehrmals ausgeführt zu werden, und die Sie an mehreren Stellen im Code benötigen, ist dies die Vorgehensweise.

KeithS
quelle
1

Hilfsmethoden haben einen Platz, aber Sie müssen vorsichtig sein, um die Konsistenz der Daten und eine unnötige Erweiterung des Variablenumfangs sicherzustellen.

In Ihrem eigenen Beispiel heißt es beispielsweise:

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;      <--- first call
    else discountFactor = 0.98;
    return basePrice() * discountFactor;                <--- second call
}

Es ist klar, dass beide _quantityund _itemPriceglobale Variablen (oder zumindest Klassenebenen) sind und daher das Potenzial besteht, dass sie außerhalb von geändert werdengetPrice()

Daher besteht die Möglichkeit, dass der erste Aufruf basePrice()einen anderen Wert als der zweite Aufruf zurückgibt!

Daher würde ich vorschlagen, dass Hilfsfunktionen nützlich sein können, um komplexe Mathematik zu isolieren, aber als Ersatz für lokale Variablen müssen Sie vorsichtig sein.


Sie müssen auch reductio ad absurdum vermeiden - sollte die Berechnung von discountFactorauf eine Methode ausgegliedert werden? So wird Ihr Beispiel:

double getPrice()
{
    final double basePrice      = calculateBasePrice();
    final double discountFactor = calculateDiscount( basePrice );

    return basePrice * discountFactor;
}

Durch die Partitionierung über eine bestimmte Ebene hinaus ist der Code weniger lesbar.

Andrew
quelle
+1, um den Code weniger lesbar zu machen. Eine übermäßige Partitionierung kann das Geschäftsproblem verbergen, das der Quellcode zu lösen versucht. Es kann spezielle Fälle geben, in denen ein Gutschein in getPrice () angewendet wird. Wenn dieser jedoch tief in einer Kette von Funktionsaufrufen verborgen ist, wird auch die Geschäftsregel ausgeblendet.
Reactgular
0

Wenn Sie zufällig in einer Sprache mit benannten Parametern (ObjectiveC, Python, Ruby usw.) arbeiten, sind temporäre Variablen weniger nützlich.

In Ihrem basePrice-Beispiel kann die Ausführung der Abfrage jedoch eine Weile dauern, und Sie möchten das Ergebnis möglicherweise für die zukünftige Verwendung in einer temporären Variablen speichern.

Wie Sie verwende ich jedoch temporäre Variablen aus Gründen der Klarheit und der Zeilenlänge.

Ich habe auch gesehen, wie Programmierer in PHP Folgendes tun. Es ist interessant und großartig zum Debuggen, aber es ist ein bisschen seltsam.

$rs = DB::query( $query = "SELECT * FROM table" );
if (DEBUG) echo $query;
// do something with $rs
Dimitry
quelle
0

Der Grund für diese Empfehlung ist, dass Sie dieselbe Vorberechnung an anderer Stelle in Ihrer Anwendung verwenden möchten. Siehe Ersetzen von Temp durch Abfrage im Refactoring-Musterkatalog:

Die neue Methode kann dann in anderen Methoden verwendet werden

    double basePrice = _quantity * _itemPrice;
    if (basePrice > 1000)
        return basePrice * 0.95;
    else
        return basePrice * 0.98;

           http://i.stack.imgur.com/mKbQM.gif

    if (basePrice() > 1000)
        return basePrice() * 0.95;
    else
        return basePrice() * 0.98;
...
double basePrice() {
    return _quantity * _itemPrice;
}

Daher würde ich in Ihrem Host- und URI-Beispiel diese Empfehlung nur anwenden, wenn ich vorhabe, dieselbe URI- oder Hostdefinition wiederzuverwenden.

Wenn dies aufgrund des Namespace der Fall ist, definiere ich keine globale uri () - oder host () -Methode, sondern einen Namen mit mehr Informationen wie twilio_host () oder archive_request_uri ().

Dann sehe ich für das Problem der Zeilenlänge mehrere Optionen:

  • Erstellen Sie eine lokale Variable wie uri = archive_request_uri().

Begründung: In der aktuellen Methode soll der URI der erwähnte sein. Die URI-Definition ist weiterhin faktorisiert.

  • Definieren Sie eine lokale Methode wie uri() { return archive_request_uri() }

Wenn Sie häufig die Empfehlung von Fowler verwenden, wissen Sie, dass die uri () -Methode dasselbe Muster aufweist.

Wenn Sie aufgrund der Wahl der Sprache mit einem 'self' auf die lokale Methode zugreifen müssen, würde ich die erste Lösung empfehlen, um die Ausdruckskraft zu erhöhen (in Python würde ich die uri-Funktion in der aktuellen Methode definieren).

Marc-Emmanuel Coupvent des Gra
quelle