Verallgemeinern Sie die Verwendung von Variablen im Code

11

Ich würde gerne wissen, ob es eine gute Praxis ist, Variablen zu verallgemeinern (verwenden Sie eine einzelne Variable, um alle Werte zu speichern).
Betrachten Sie ein einfaches Beispiel

 Strings querycre,queryins,queryup,querydel; 
    querycre = 'Create table XYZ ...';
    execute querycre ;
    queryins = 'Insert into XYZ ...';
    execute queryins ;
    queryup  = 'Update  XYZ set ...';
    execute queryup;
    querydel = 'Delete from XYZ ...';
    execute querydel ;

und

 Strings query; 
    query= 'Create table XYZ ... ';
    execute query ;
    query= 'Insert into XYZ ...';
    execute query ;
    query= 'Update  XYZ set ...';
    execute query ;
    query= 'Delete from XYZ ...';
    execute query ;

Im ersten Fall verwende ich 4 Zeichenfolgen, in denen jeweils Daten gespeichert sind, um die in ihren Suffixen genannten Aktionen auszuführen.
Im zweiten Fall nur 1 Variable zum Speichern aller Arten der Daten.
Unterschiedliche Variablen erleichtern es anderen, sie besser zu lesen und zu verstehen. Aber zu viele von ihnen zu haben, macht es schwierig, damit umzugehen.

Beeinträchtigen zu viele Variablen auch meine Leistung?

PS: Bitte antworten Sie nicht auf den Code im Beispiel, sondern nur, um zu vermitteln, was ich wirklich meine.

Shirish11
quelle
Natürlich verwenden Sie dieselbe Variable wieder ... weil Sie sie in einer Funktion definiert haben. Dafür sind Funktionen da.
zzzzBov

Antworten:

26

Sich diese Frage stellen zu müssen, ist ein ziemlich starker Geruch, dem Sie DRY nicht folgen (Wiederholen Sie sich nicht). Angenommen, Sie haben dies in einer hypothetischen Sprache mit geschweiften Klammern:

function doFoo() {
    query = "SELECT a, b, c FROM foobar WHERE baz = 23";
    result = runQuery(query);
    print(result);

    query = "SELECT foo, bar FROM quux WHERE x IS NULL";
    result = runQuery(query);
    print(result);

    query = "SELECT a.foo, b.bar FROM quux a INNER JOIN quuux b ON b.quux_id = a.id ORDER BY date_added LIMIT 10";
    result = runQuery(query);
    print(result);
}

Refactor das in:

function runAndPrint(query) {
    result = runQuery(query);
    print(result);
}

function doFoo() {
    runAndPrint("SELECT a, b, c FROM foobar WHERE baz = 23");
    runAndPrint("SELECT foo, bar FROM quux WHERE x IS NULL");
    runAndPrint("SELECT a.foo, b.bar FROM quux a INNER JOIN quuux b ON b.quux_id = a.id ORDER BY date_added LIMIT 10");
}

Beachten Sie, dass Sie nicht mehr entscheiden müssen, ob Sie verschiedene Variablen verwenden möchten oder nicht, und wie Sie jetzt die Logik zum Ausführen einer Abfrage und zum Drucken des Ergebnisses an einer Stelle ändern können, anstatt dieselbe Änderung dreimal anwenden zu müssen. (Sie können beispielsweise entscheiden, dass Sie das Abfrageergebnis durch ein Vorlagensystem pumpen möchten, anstatt es sofort zu drucken.)

tdammers
quelle
2
Ich liebe das DRY-Prinzip :)
Artjom
1
@tdammers ist es gut, nur 2 Codezeilen in einer Funktion zu haben? Überlegen Sie, ob ich diese Funktion habe. doFoo () {print (runQuery ("Wählen Sie a, b, c aus XYZ"));}
Shirish11
1
Nein, der Aufrufstapel wird nicht erhöht. Bei jedem Aufruf wird runAndPrintein Stapelrahmen beim Aufrufen verschoben und beim Beenden der Funktion zurückgesetzt. Wenn Sie es dreimal aufrufen, werden drei Push / Pop-Paare ausgeführt, aber der Stapel wächst nie um mehr als einen Frame gleichzeitig. Sie sollten sich nur mit rekursiven Funktionen wirklich um die Tiefe des Aufrufstapels kümmern.
tdammers
3
Und Funktionen mit nur zwei Codezeilen sind vollkommen in Ordnung: Wenn zwei Zeilen eine logische Einheit bilden, sind es zwei Zeilen. Ich habe viele Einzeiler-Funktionen geschrieben, um ein paar Informationen isoliert und an einem Ort zu halten.
tdammers
1
@ JamesAnderson: Es ist ein etwas erfundenes Beispiel, aber es dient dazu, einen Punkt zu veranschaulichen. Es geht nicht darum, wie viele Codezeilen Sie haben. Es ist, wie oft Sie die gleiche Tatsache angeben. Darum geht es bei DRY ebenso wie um das Prinzip der
einzigen
14

Normalerweise ist dies eine schlechte Praxis.

Die Wiederverwendung einer Variablen kann auf diese Weise zu verwirrendem Code führen.

Diejenigen, die den Code lesen, erwarten nicht, dass eine Variable auf diese Weise wiederverwendet wird, und wissen nicht, warum ein am Anfang festgelegter Wert am Ende der Funktion einen anderen Wert hat.

Die Beispiele, die Sie veröffentlicht haben, sind sehr einfach und leiden nicht wirklich unter diesem Problem, aber sie sind nicht repräsentativ für Code, der Variablen wiederverwendet (wo er zu Beginn festgelegt wird, wird irgendwo in der Mitte wiederverwendet - außer Sichtweite).

Die Beispiele, die Sie angegeben haben, eignen sich für die Kapselung in Funktionen, in denen Sie die Abfrage übergeben und ausführen würden.

Oded
quelle
Was ist mit der Systemleistung, die davon beeinflusst wird?
Shirish11
@ Shirish11 - Es könnte. Hängt vom Compiler, der Sprache, der Umgebung und anderen Variablen ab.
Oded
Normalerweise kann der Compiler dies gut optimieren. Dies hängt jedoch immer vom Compiler / der Plattenform / dem speziellen Fall / der Konfiguration ab.
Deadalnix
7

Selbstdokumentierter Code ist einfacher zu lesen und zu warten

Befolgen Sie das Prinzip der geringsten Versöhnung und das Gebot des Codes als Dokumentation : Verwenden Sie eine Variable für ein Ziel, um sowohl die Verwendung verständlich als auch den Code ohne Erklärungen leicht lesbar zu machen.

Richtig strukturierter Code ist einfacher (also billiger) zu (wieder) verwenden

Auch hier scheint es, dass queryimmer verwendet wird, um eine Anweisung vorzubereiten, bevor sie ausgeführt wird. Dies ist wahrscheinlich ein Zeichen dafür, dass Sie einen Teil dieses Codes in eine (oder mehrere) Hilfsmethoden umgestalten möchten, um die Abfrage vorzubereiten und auszuführen (um dem DRY-Prinzip zu entsprechen ).

Auf diese Weise werden Sie effektiv:

  • Verwenden Sie nur eine Variable in Ihrer Hilfsmethode, um die Abfrage des aktuellen Kontexts zu identifizieren.
  • Sie müssen jedes Mal weniger Code eingeben, wenn Sie eine Abfrage erneut ausführen möchten.
  • Machen Sie Ihren Code für andere lesbarer.

Beispiele:

Betrachten Sie dies aus Ihrem Beispiel, wo die überarbeitete Version offensichtlich besser ist. Natürlich war Ihr Snippet nur ein Beispiel für den Zweck dieser Frage, aber das Konzept gilt immer noch und skaliert.

Ihr Beispiel 1:

Strings querycre,queryins,queryup,querydel; 
    querycre = 'Create table XYZ ...';
    execute querycre ;
    queryins = 'Insert into XYZ ...';
    execute queryins ;
    queryup  = 'Update  XYZ set ...';
    execute queryup;
    querydel = 'Delete from XYZ ...';
    execute querydel ;

Ihr Beispiel 2:

 Strings query; 
    query= 'Create table XYZ ...';
    execute query ;
    query= 'Insert into XYZ ...';
    execute query ;
    query= 'Update  XYZ set ...';
    execute query ;
    query= 'Delete from XYZ ...';
    execute query ;

Beispiel 3 (Überarbeiteter Pseudocode):

def executeQuery(query, parameters...)
    statement = prepareStatement(query, parameters);
    execute statement;
end

// call point:
executeQuery('Create table XYZ ... ');
executeQuery('Insert into XYZ ...');
executeQuery('Update  XYZ set ...');
executeQuery('Delete from XYZ ...');

Der Nutzen zeigt sich bei regelmäßiger Wiederverwendung.

Persönliche Anekdote

Ich habe ursprünglich als C-Programmierer angefangen, der mit begrenzten Bildschirmflächen arbeitete. Daher war die Wiederverwendung von Variablen sowohl für den kompilierten Code (damals) als auch für die sofortige Lesbarkeit von mehr Code sinnvoll.

Nachdem ich mich dann den höheren Sprachen zugewandt und die funktionale Programmierung aufgefrischt hatte, habe ich mir angewöhnt, unveränderliche Variablen und unveränderliche Referenzen zu verwenden, wo immer dies möglich war, um Nebenwirkungen zu begrenzen.

Was ist drin für mich?

Wenn Sie es sich zur Gewohnheit machen, dass alle Eingaben Ihrer Funktion unveränderlich sind und ein neues Ergebnis zurückgeben (wie es eine echte mathematische Funktion tun würde), gewöhnen Sie sich an, Speicher nicht zu duplizieren.

Im weiteren Sinne führt dies zu:

  • Sie schreiben kurze Funktionen,
  • mit klar definierten Zielen,
  • das sind leichter zu verstehen,
  • wiederverwenden,
  • zu erweitern (ob durch OO-Vererbung oder durch funktionelle Verkettung),
  • und dokumentieren (wie bereits selbstdokumentierend).

Ich sage hier nicht, dass ein veränderlicher Zustand keinen Nutzen bringt. Ich möchte nur darauf hinweisen, wie sich die Gewohnheit auf Sie auswirken könnte und wie sich dies auf die Lesbarkeit des Codes auswirkt.

Haylem
quelle
2

In Bezug auf Code-Design

Im Allgemeinen ist es in Ordnung, Variablen zum Speichern unterschiedlicher Werte wiederzuverwenden - schließlich werden sie deshalb als Variablen bezeichnet, da der darin gespeicherte Wert variiert - , solange der Wert nicht nur vom gleichen Typ ist, sondern auch dasselbe bedeutet . Zum Beispiel ist es natürlich in Ordnung, die currentQueryVariable hier wiederzuverwenden :

for currentQuery in queries:
    execute query;

Natürlich gibt es eine Schleife , so dass Sie haben eine Variable wieder zu verwenden, aber selbst wenn es nicht eine Schleife ist wäre es in Ordnung gewesen. Wenn der Wert nicht dasselbe bedeutet, verwenden Sie eine separate Variable.

Insbesondere sieht der von Ihnen beschriebene Code jedoch nicht sehr gut aus - er wiederholt sich . Es ist viel besser, Schleifen- oder Hilfsmethodenaufrufe (oder beides) zu verwenden. Persönlich habe ich sehr selten Produktionscode gesehen, der entweder Ihrer 1. oder 2. Version ähnelt, aber in den Fällen, die ich habe, war die 2. Version (variable Wiederverwendung) meiner Meinung nach häufiger.

In Bezug auf die Leistung

Es hängt von der Sprache, den verwendeten Compilern und Laufzeitsystemen ab, aber im Allgemeinen sollte es keinen Unterschied geben - insbesondere Compiler für stapelbasierte Registermaschinen (wie das beliebte x86 / x86-64) werden es sowieso nur tun Verwenden Sie einen beliebigen freien Stapelspeicher oder ein beliebiges Register als Zuweisungsziel und ignorieren Sie dabei vollständig, ob Sie dieselbe Variable möchten oder nicht.

Zum Beispiel gcc -O2erzeugt die exakt gleiche binäre, und der einzige Unterschied in der Leistung ich kenne , ist die Größe der Symboltabelle während der Kompilierung - völlig zu vernachlässigen , wenn Sie in die Zeit der 60er Jahre zurückgehen.

Ein Java-Compiler generiert Bytecode, der mehr Speicher für die 1. Version benötigt, aber der Jitter der JVM entfernt ihn trotzdem. Ich vermute also, dass es praktisch keine spürbaren Auswirkungen auf die Leistung gibt, selbst wenn Sie hochoptimierten Code benötigen.

Eiche
quelle
0

Ich denke, die Wiederverwendung der Variablen ist die meiste Zeit in Ordnung.

Für mich verwende ich die Abfragevariable die meiste Zeit einfach wieder. Ich führe die Abfrage fast immer direkt danach aus. Wenn ich die Abfrage nicht sofort ausführe, verwende ich normalerweise einen anderen Variablennamen.

Echo sagt Reinstate Monica
quelle
-1

Es kann die Stack-Nutzung erhöhen, wenn Ihr Compiler besonders dumm ist. Persönlich glaube ich nicht, dass eine separate Variable für jede Abfrage die Lesbarkeit verbessert. Sie müssen sich dennoch die Abfragezeichenfolge ansehen, um zu sehen, was sie bewirkt.

James
quelle
Ich habe gerade ein einfaches Beispiel gegeben, damit die Leser leichter verstehen, was ich suche. Mein Code ist viel komplexer als dieser.
Shirish11
-2

Im Beispiel würde ich mit dem zweiten Beispiel gehen. Sowohl für einen Leser als auch für Optimierer ist klar, was Sie tun. Das erste Beispiel ist etwas korrekter und mit etwas komplizierterem Code würde ich es verwenden, aber mache es wie folgt:

{
    String query = 'Create table XYZ ...';
    execute query;
}
{
    String query = 'Insert table XYZ ...';
    execute query;
}
And so on...

(An dieser Stelle könnte ich die Lösung von tdammers in Betracht ziehen .)

Das Problem mit dem ersten Beispiel besteht darin, dass querycrees sich um einen Bereich für den gesamten Block handelt, der möglicherweise umfangreich ist. Dies kann jemanden verwirren, der den Code liest. Es kann auch die Optimierer verwirren, die möglicherweise einen unnötigen Speicherschreibvorgang hinterlassen, sodass querycresie später bei Bedarf verfügbar sind (was nicht der Fall ist). queryWird mit allen Klammern nur in einem Register gespeichert, wenn das so ist.

Bei Phrasen wie "Tabelle erstellen" und "Ausführen" sieht es für mich nicht so aus, als würde hier ein zusätzlicher Speicherschreibvorgang bemerkt, daher würde ich den Code nur bemängeln, um den Leser zu verwirren. Aber es ist praktisch , um sich bewusst sein , wenn Sie dieses Schreiben von Code , wo die Geschwindigkeit tut Angelegenheit.

RalphChapin
quelle
Ich stimme dir nicht zu. Wenn Sie das zweite Beispiel aus Gründen der Übersichtlichkeit bevorzugen, sollte es auf aufeinanderfolgende Aufrufe einer Hilfsmethode umgestaltet werden. es würde mehr Bedeutung vermitteln und weniger Code erfordern.
Haylem
@ Haylem: In einem wirklich einfachen Fall wie diesem fügen Sie eine Hilfsmethode hinzu, die jemand, der den Code liest, suchen muss. (Und jemand hat möglicherweise Probleme mit der Hilfsmethode und muss alle Stellen herausfinden, von denen aus er aufgerufen wird.) Weniger Klarheit, ungefähr die gleiche Menge an Code. In einem komplizierteren Fall würde ich mit meiner Lösung gehen, dann mit der von tdammer . Ich habe diese Frage hauptsächlich beantwortet, um auf die (zugegebenermaßen obskuren, aber interessanten) Probleme hinzuweisen, die nicht genutzte Variablen sowohl für Menschen als auch für Optimierer darstellen.
RalphChapin
@ Haylem: Sie und tdammer geben beide die richtige Lösung. Ich denke nur, dass es in einigen Fällen übertrieben sein kann.
RalphChapin