Umfasst „Variablen sollten so klein wie möglich sein“ den Fall „Variablen sollten möglichst nicht existieren“?

32

Laut der akzeptierten Antwort zu " Argumentation, lokale Variablen gegenüber Instanzvariablen zu bevorzugen? " Sollten Variablen im kleinstmöglichen Bereich leben.
Vereinfache das Problem in meiner Interpretation, es bedeutet, dass wir diese Art von Code überarbeiten sollten:

public class Main {
    private A a;
    private B b;

    public ABResult getResult() {
        getA();
        getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      a = SomeFactory.getA();
    }

    private getB() {
      b = SomeFactory.getB();
    }
}

in so etwas:

public class Main {
    public ABResult getResult() {
        A a = getA();
        B b = getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}

aber nach dem "Geist" von "Variablen sollten möglichst im kleinsten Bereich leben", haben "niemals Variablen" nicht einen kleineren Bereich als "Variablen"? Daher denke ich, dass die obige Version überarbeitet werden sollte:

public class Main {
    public ABResult getResult() {
        return ABFactory.mix(getA(), getB());
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}

das getResult()hat also überhaupt keine lokalen Variablen. Ist das wahr?

ocomfd
quelle
72
Das Erstellen expliziter Variablen hat den Vorteil, dass Sie diese benennen müssen. Das Einführen einiger Variablen kann eine undurchsichtige Methode schnell in eine lesbare verwandeln.
Jared Goguen
11
Ehrlich gesagt ist ein Beispiel in Bezug auf die Variablennamen a und b zu erfunden, um den Wert lokaler Variablen zu demonstrieren. Zum Glück hast du trotzdem gute Antworten bekommen.
Doc Brown
3
In Ihrem zweiten Snippet sind Ihre Variablen keine wirklichen Variablen . Sie sind effektiv lokale Konstanten, da Sie sie nicht ändern. Der Java-Compiler behandelt sie gleich, wenn Sie sie als endgültig definiert haben, da sie sich im lokalen Bereich befinden und der Compiler weiß, was mit ihnen geschehen wird. Es ist eine Frage des Stils und der Meinung, ob Sie tatsächlich ein finalSchlüsselwort verwenden sollten oder nicht.
Hyde
2
@JaredGoguen Das Erstellen expliziter Variablen hat die Last, sie benennen zu müssen und den Vorteil, sie benennen zu können.
Bergi
2
Absolut null von Codestilregeln wie "Variablen sollten im kleinstmöglichen Bereich leben" sind universell anwendbar, ohne darüber nachzudenken. Aus diesem Grund möchten Sie eine Entscheidung im Codestil niemals auf die Begründung des Formulars in dieser Frage stützen, indem Sie eine einfache Richtlinie verwenden und sie auf einen weniger offensichtlich abgedeckten Bereich ausweiten ("Variablen sollten möglichst kleine Bereiche haben" ->) msgstr "Variablen sollten möglichst nicht existieren"). Entweder gibt es gute Gründe, die Ihre Schlussfolgerung spezifisch stützen, oder Ihre Schlussfolgerung ist eine unangemessene Erweiterung. In beiden Fällen benötigen Sie die ursprüngliche Richtlinie nicht.
Ben

Antworten:

110

Nein. Es gibt mehrere Gründe, warum:

  1. Variablen mit aussagekräftigen Namen können das Verstehen von Code erleichtern.
  2. Das Aufteilen komplexer Formeln in kleinere Schritte kann das Lesen des Codes erleichtern.
  3. Caching.
  4. Halten Sie Verweise auf Objekte, damit diese mehrmals verwendet werden können.

Und so weiter.

Robert Harvey
quelle
22
Erwähnenswert ist auch, dass der Wert unabhängig davon im Speicher abgelegt wird, sodass er ohnehin den gleichen Gültigkeitsbereich hat. Kannst du es auch nennen (aus den Gründen, die Robert oben erwähnt hat)!
Vielleicht_Faktor
5
@Maybe_Factor Die Richtlinie ist aus Leistungsgründen nicht wirklich vorhanden. Es geht darum, wie einfach es ist, den Code zu pflegen. Das bedeutet aber immer noch, dass bedeutungsvolle Einheimische besser sind als ein großer Ausdruck, es sei denn, Sie komponieren lediglich gut benannte und gestaltete Funktionen (z. B. gibt es keinen Grund dazu var taxIndex = getTaxIndex();).
12.
16
Alle sind wichtige Faktoren. So wie ich es sehe: Kürze ist ein Ziel; Klarheit ist eine andere; Robustheit ist eine andere; Leistung ist eine andere; und so weiter. Keines davon ist ein absolutes Ziel , und manchmal kommt es zu Konflikten. Wir müssen also herausfinden, wie wir diese Ziele am besten in Einklang bringen können .
gidds
8
Der Compiler kümmert sich um 3 & 4.
Beende Monica
9
Nummer 4 kann für die Richtigkeit wichtig sein. Wenn sich der Wert eines Funktionsaufrufs ändern kann (z. B. now()), kann das Entfernen der Variablen und das mehrmalige Aufrufen der Methode zu Fehlern führen. Dies kann zu einer Situation führen, die sehr subtil und schwer zu debuggen ist. Es mag offensichtlich erscheinen, aber wenn Sie sich in einer Blind-Refactoring-Mission befinden, um Variablen zu entfernen, kann es leicht zu Fehlern kommen.
JimmyJames
16

Stimmen Sie zu, Variablen, die nicht erforderlich sind und die Lesbarkeit des Codes nicht verbessern, sollten vermieden werden. Je mehr Variablen an einem bestimmten Punkt im Code im Geltungsbereich sind, desto komplexer ist der Code zu verstehen.

Ich sehe nicht wirklich den Nutzen der Variablen aund bin Ihrem Beispiel würde ich die Version ohne Variablen schreiben. Andererseits ist die Funktion so einfach, dass ich nicht denke, dass es wichtig ist.

Je länger die Funktion wird und je mehr Variablen im Gültigkeitsbereich sind, desto problematischer wird es.

Zum Beispiel, wenn Sie haben

    a=getA();
    b=getB();
    m = ABFactory.mix(a,b);

An der Spitze einer größeren Funktion erhöhen Sie die mentale Belastung, den Rest des Codes zu verstehen, indem Sie statt einer drei Variablen einführen. Sie müssen den Rest des Codes durchlesen, um zu sehen, ob aoder bwieder verwendet werden. Einheimische, deren Umfang länger ist als erforderlich, beeinträchtigen die allgemeine Lesbarkeit.

Wenn eine Variable erforderlich ist (z. B. um ein temporäres Ergebnis zu speichern) oder wenn eine Variable die Lesbarkeit des Codes verbessert, sollte sie natürlich beibehalten werden.

JacquesB
quelle
2
Natürlich wird eine Methode so lang, dass die Anzahl der Einheimischen zu groß ist, um leicht gewartet werden zu können, und Sie möchten sie wahrscheinlich trotzdem auflösen.
12.
4
Zu den Vorteilen von "fremden" Variablen var result = getResult(...); return result;gehört, dass Sie einen Haltepunkt setzen returnund herausfinden können, was genau das resultist.
Joker_vD
5
@Joker_vD: Ein Debugger, der seinen Namen verdient, sollte in der Lage sein, all dies zu tun (die Ergebnisse eines Funktionsaufrufs anzeigen, ohne ihn in einer lokalen Variablen zu speichern + einen Haltepunkt beim Funktionsexit setzen).
Christian Hackl
2
@ChristianHackl Nur sehr wenige Debugger ermöglichen dies, ohne möglicherweise komplexe Uhren einzurichten, deren Hinzufügen einige Zeit in Anspruch nimmt. Ich nehme die with variables-Version zum Debuggen an jedem Tag der Woche.
Gabe Sechan
1
@ChristianHackl und um weiter darauf aufzubauen, selbst wenn der Debugger dies nicht standardmäßig zulässt, weil der Debugger schrecklich konstruiert ist, können Sie einfach den lokalen Code auf Ihrem Computer ändern, um das Ergebnis zu speichern, und es dann im Debugger auschecken . Es gibt noch keinen Grund , einen Wert in einer Variablen zu speichern , für die nur diesen Zweck.
Die große Ente
11

Neben den anderen Antworten möchte ich noch auf etwas anderes hinweisen. Der Vorteil, den Gültigkeitsbereich einer Variablen klein zu halten, besteht nicht nur darin, dass weniger Code syntaktisch auf die Variable zugreifen kann, sondern auch darin, dass die Anzahl der möglichen Steuerungsflusspfade verringert wird, die eine Variable möglicherweise ändern können (entweder durch Zuweisen eines neuen Werts oder durch Aufrufen) eine Mutationsmethode für das vorhandene Objekt in der Variablen).

Klassenbezogene Variablen (Instanzen oder statische Variablen) verfügen über wesentlich mehr mögliche Kontrollflusspfade als lokale Variablen, da sie durch Methoden mutiert werden können, die in beliebiger Reihenfolge, beliebig oft und häufig durch Code außerhalb der Klasse aufgerufen werden können .

Werfen wir einen Blick auf Ihre ursprüngliche getResultMethode:

public ABResult getResult() {
    getA();
    getB();
    return ABFactory.mix(this.a, this.b);
}

Nun werden die Namen getAund getBkann vorschlagen , dass sie vergeben werden this.aund this.bkönnen wir nicht wissen , sicher von einem Blick auf getResult. Daher ist es möglich, dass die an die Methode übergebenen Werte this.aund stattdessen aus dem Zustand des Objekts stammen, von dem this.bzuvor die mixMethode aufgerufen wurde. Dies kann nicht vorhergesagt werden, da Clients steuern, wie und wann Methoden aufgerufen werden.thisgetResult

In dem überarbeiteten Code mit den Variablen local aund bist klar, dass es genau einen (ausnahmefreien) Kontrollfluss von der Zuweisung jeder Variablen zu ihrer Verwendung gibt, da die Variablen unmittelbar vor ihrer Verwendung deklariert werden.

Das Verschieben (modifizierbarer) Variablen aus dem Klassenbereich in den lokalen Bereich sowie das Verschieben (modifizierbarer) Variablen von der Außenseite einer Schleife nach innen hat daher einen erheblichen Vorteil, da das Schließen des Kontrollflusses vereinfacht wird.

Andererseits hat das Eliminieren von Variablen wie in Ihrem letzten Beispiel einen geringeren Vorteil, da es die Argumentation für den Kontrollfluss nicht wirklich beeinflusst. Sie verlieren auch die Namen der Werte, was nicht der Fall ist, wenn Sie eine Variable einfach in einen inneren Bereich verschieben. Dies ist ein Kompromiss, den Sie berücksichtigen müssen, sodass die Eliminierung von Variablen in einigen Fällen besser und in anderen schlechter sein kann.

Wenn Sie die Variablennamen nicht verlieren, aber dennoch den Gültigkeitsbereich der Variablen verringern möchten (falls sie in einer größeren Funktion verwendet werden), können Sie die Variablen und ihre Verwendung in eine Blockanweisung einschließen ( oder sie in ihre eigene Funktion versetzen ).

YawarRaza7349
quelle
2

Dies ist etwas sprachabhängig, aber ich würde sagen, dass einer der weniger offensichtlichen Vorteile der funktionalen Programmierung darin besteht, dass Programmierer und Leser von Code ermutigt werden, diese nicht zu benötigen. Erwägen:

(reduce (fn [map string] (assoc map string (inc (map string 0))) 

Oder etwas LINQ:

var query2 = mydb.MyEntity.Select(x => x.SomeProp).AsEnumerable().Where(x => x == "Prop");

Oder Node.js:

 return visionFetchLabels(storageUri)
    .then(any(isCat))
    .then(notifySlack(secrets.slackWebhook, `A cat was posted: ${storageUri}`))
    .then(logSuccess, logError)
    .then(callback)

Die letzte ist eine Kette von Aufrufen einer Funktion für das Ergebnis einer vorherigen Funktion ohne Zwischenvariablen. Ihre Einführung würde es viel unklarer machen.

Der Unterschied zwischen dem ersten und den beiden anderen Beispielen ist jedoch die implizite Reihenfolge der Operationen . Dies ist möglicherweise nicht die gleiche Reihenfolge wie die tatsächlich berechnete, aber die Reihenfolge, in der der Leser darüber nachdenken sollte. Für die zweiten beiden ist dies von links nach rechts. Für das Lisp / Clojure-Beispiel ist es eher von rechts nach links. Sie sollten etwas vorsichtig sein, wenn Sie Code schreiben, der nicht in der "Standardrichtung" für Ihre Sprache ist, und Ausdrücke mit "mittlerer Ausprägung", die beides vermischen, sollten auf jeden Fall vermieden werden.

Der Pipe-Operator von F # |>ist unter anderem deshalb nützlich, weil Sie damit von links nach rechts schreiben können, was sonst von rechts nach links erfolgen müsste.

pjc50
quelle
2
In einfachen LINQ-Ausdrücken oder Lambdas verwende ich Unterstriche als Variablennamen. myCollection.Select(_ => _.SomeProp).Where(_ => _.Size > 4);
Graham
Ich bin mir nicht sicher, ob dies die Frage von OP im Geringsten beantwortet ...
AC
1
Warum die Abstimmungen? Scheint mir eine gute Antwort zu sein, auch wenn es die Frage nicht direkt beantwortet, sind es dennoch nützliche Informationen
reggaeguitar
1

Ich würde nein sagen, weil Sie "kleinstmöglichen Bereich" als "unter vorhandenen Bereichen oder solchen, die sinnvoll sind, hinzuzufügen" lesen sollten. Andernfalls müssten Sie künstliche Bereiche erstellen (z. B. unbegründete {}Blöcke in C-ähnlichen Sprachen), um sicherzustellen, dass der Bereich einer Variablen nicht über den letzten Verwendungszweck hinausgeht, und dies würde im Allgemeinen als Verschleierung / Unordnung angesehen, es sei denn, dies ist bereits geschehen Ein guter Grund für die Unabhängigkeit des Geltungsbereichs.

R ..
quelle
2
Ein Problem beim Scoping in vielen Sprachen ist, dass es sehr häufig vorkommt, dass einige Variablen zuletzt für die Berechnung der Anfangswerte anderer Variablen verwendet werden. Wenn eine Sprache Konstrukte hatte, die explizit zum Zweck des Gültigkeitsbereichs bestimmt waren und die die Verwendung von Werten zuließen, die unter Verwendung von Variablen innerhalb des Gültigkeitsbereichs bei der Initialisierung von Variablen außerhalb des Gültigkeitsbereichs berechnet wurden, könnten solche Gültigkeitsbereiche nützlicher sein als die streng verschachtelten Gültigkeitsbereiche, die viele Sprachen benötigen.
Superkatze
1

Betrachten Sie Funktionen ( Methoden ). Dort wird weder der Code in die kleinstmögliche Teilaufgabe aufgeteilt, noch das größte einzelne Codestück.

Es ist eine Verschiebungsgrenze, bei der logische Aufgaben in Verbrauchsgüter unterteilt werden.

Gleiches gilt für Variablen . Aufzeigen logischer Datenstrukturen in verständliche Teile. Oder benennen Sie einfach die Parameter:

boolean automatic = true;
importFile(file, automatic);

Aber natürlich mit einer Erklärung oben und zweihundert Zeilen weiter wird die erste Verwendung heutzutage als schlechter Stil akzeptiert. Dies ist eindeutig das, was "Variablen im kleinstmöglichen Rahmen leben sollten" zu sagen beabsichtigt. Wie ein sehr nahes "Variablen nicht wiederverwenden."

Joop Eggen
quelle
1

Was als Grund für NEIN etwas fehlt, ist Debugging / Readabillity. Code sollte dafür optimiert sein und klare und prägnante Namen helfen viel, zB stellen Sie sich eine 3-Wege-Lösung vor

if (frobnicate(x) && (get_age(x) > 2000 || calculate_duration(x) < 100 )

Diese Zeile ist kurz, aber schon schwer zu lesen. Fügen Sie ein paar weitere Parameter hinzu, und das, wenn Sie sich über mehrere Zeilen erstrecken.

can_be_frobnicated = frobnicate(x)
is_short_lived_or_ancient = get_age(x) > 2000 || calculate_duration(x) < 100
if (can_be_frobnicated || is_short_lived_or_ancient )

Ich finde diesen Weg einfacher zu lesen und Bedeutung zu kommunizieren - so habe ich kein Problem mit Zwischenvariablen.

Ein anderes Beispiel wären Sprachen wie R, bei denen die letzte Zeile automatisch der Rückgabewert ist:

some_func <- function(x) {
    compute(x)
}

das ist gefährlich, ist die rückgabe zu erwarten oder wird sie benötigt? das ist klarer:

some_func <- function(x) {
   rv <- compute(x)
   return(rv)
}

Wie immer handelt es sich hierbei um einen Beurteilungsaufruf - eliminieren Sie Zwischenvariablen, wenn sie das Lesen nicht verbessern, andernfalls behalten oder einführen.

Ein weiterer Punkt kann die Debug-Fähigkeit sein: Wenn die Zwischenergebnisse von Interesse sind, kann es am besten sein, einfach einen Zwischenhändler einzuführen, wie im obigen R-Beispiel. Wie oft dies aufgerufen wird, ist schwer vorstellbar und Sie müssen vorsichtig sein, was Sie einchecken - zu viele Debugging-Variablen sind verwirrend -.

Christian Sauer
quelle
0

Nur unter Bezugnahme auf Ihren Titel: Wenn eine Variable nicht benötigt wird, sollte sie unbedingt gelöscht werden.

Aber "unnötig" bedeutet nicht, dass ein äquivalentes Programm ohne Verwendung der Variablen geschrieben werden kann, sonst würde uns gesagt, dass wir alles in Binärform schreiben sollten.

Die am häufigsten vorkommende Art von unnötiger Variable ist eine nicht verwendete Variable. Je kleiner der Gültigkeitsbereich der Variablen ist, desto einfacher lässt sich feststellen, dass sie nicht erforderlich ist. Ob eine Zwischenvariable unnötig ist, ist schwieriger zu bestimmen, da es sich nicht um eine binäre Situation handelt, sondern um eine kontextbezogene. Tatsächlich könnte identischer Quellcode in zwei verschiedenen Methoden eine unterschiedliche Antwort desselben Benutzers ergeben, abhängig von den Erfahrungen in der Vergangenheit bei der Behebung von Problemen im umgebenden Code.

Wenn Ihr Beispielcode genau so wäre, wie er dargestellt wurde, würde ich vorschlagen, die beiden privaten Methoden loszuwerden, hätte aber kaum Bedenken, ob Sie das Ergebnis der Factory-Aufrufe für eine lokale Variable gespeichert oder sie nur als Argumente für die Mischung verwendet haben Methode.

Die Lesbarkeit des Codes übertrifft alles außer der korrekten Funktionsweise (enthält korrekterweise akzeptable Leistungskriterien, die selten „so schnell wie möglich“ sind).

jmoreno
quelle