Généraliser l'utilisation des variables dans le code

11

Je voudrais savoir si c'est une bonne pratique de généraliser des variables (utilisez une seule variable pour stocker toutes les valeurs).
Prenons un exemple simple

 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 ;

et

 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 ;

Dans le premier cas, j'utilise 4 chaînes stockant chacune des données pour effectuer les actions mentionnées dans leurs suffixes.
Dans le deuxième cas, une seule variable pour stocker toutes sortes de données.
Le fait d'avoir des variables différentes facilite la lecture et la compréhension par quelqu'un d'autre. Mais en avoir trop, c'est difficile à gérer.

Est-ce que le fait d'avoir trop de variables entrave mes performances?

PS: s'il vous plaît ne répondez pas par exemple au code, c'était juste pour transmettre ce que je veux vraiment dire.

Shirish11
la source
Bien sûr, vous réutilisez la même variable ... parce que vous l'avez définie dans une fonction. C'est à cela que servent les fonctions.
zzzzBov

Réponses:

26

Devoir se poser cette question est une odeur assez forte que vous ne suivez pas SEC (ne vous répétez pas). Supposons que vous ayez ceci, dans un langage hypothétique à accolades:

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);
}

Remanier cela en:

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");
}

Remarquez comment le besoin de décider d'utiliser ou non différentes variables disparaît et comment vous pouvez maintenant changer la logique pour exécuter une requête et imprimer le résultat au même endroit, plutôt que d'avoir à appliquer la même modification trois fois. (Par exemple, vous pouvez décider de pomper le résultat de la requête via un système de modèles au lieu de l'imprimer immédiatement).

tdammers
la source
2
J'adore le principe DRY :)
artjom
1
@tdammers est-il bon d'avoir seulement 2 lignes de code à l'intérieur d'une fonction? considérez si j'ai cette fonction doFoo () {print (runQuery ("Selct a, b, c from XYZ"));}
Shirish11
1
Non, la pile d'appels n'augmente pas - chaque appel runAndPrintpousse une trame de pile lorsque vous l'appelez, puis la fait revenir lorsque la fonction se ferme. Si vous l'appelez trois fois, il fera trois paires push / pop, mais la pile ne s'agrandit jamais de plus d'une image à la fois. Vous ne devriez vraiment vous soucier de la profondeur de la pile d'appels qu'avec des fonctions récursives.
tdammers
3
Et les fonctions avec seulement deux lignes de code sont parfaitement bien: si deux lignes forment une unité logique, alors c'est deux lignes. J'ai écrit de nombreuses fonctions à une ligne, juste pour garder un peu d'informations isolées et en un seul endroit.
tdammers
1
@JamesAnderson: C'est un exemple quelque peu artificiel, mais il sert à illustrer un point. Il ne s'agit pas du nombre de lignes de code dont vous disposez. C'est combien de fois vous énoncez le même fait. C'est ce dont traite DRY, ainsi que le principe de la source unique de vérité, la règle Tu ne dois pas copier-coller, etc.
tdammers
14

Normalement, c'est une mauvaise pratique.

Réutiliser une variable de cette manière peut rendre le code déroutant pour lire et comprendre.

Ceux qui lisent le code ne s'attendront pas à ce qu'une variable soit réutilisée de cette manière et ne sauront pas pourquoi une valeur définie au début a une valeur différente à la fin de la fonction.

Les exemples que vous avez publiés sont très simples et ne souffrent pas vraiment de ce problème, mais ils ne sont pas représentatifs d'un code qui réutilise des variables (où il est défini au début, est réutilisé quelque part au milieu - à l'abri des regards).

Les exemples que vous avez donnés se prêtent à l'encapsulation dans des fonctions, où vous passeriez la requête et l'exécuteriez.

Oded
la source
qu'en est-il des performances du système?
Shirish11
@ Shirish11 - C'est possible. Dépend du compilateur, du langage, de l'environnement et d'autres variables.
Odé le
Habituellement, le compilateur est bon pour optimiser cela. Cependant, cela dépend toujours du compilateur / de la plateforme / du cas spécifique / de la configuration.
deadalnix
7

Le code auto-documenté est plus facile à lire et à entretenir

Suivez le principe de la moindre expiation et du précepte du code en tant que documentation : utilisez une variable pour un objectif, à la fois pour rendre son utilisation facile à comprendre et le code facile à lire sans explications.

Un code correctement structuré est plus facile (donc moins cher) à (ré) utiliser

Ici aussi, il semblerait que ce querysoit toujours utilisé pour préparer une instruction avant de l'exécuter. C'est probablement un signe que vous souhaitez refactoriser une partie de ce code en une (ou plusieurs) méthodes d'assistance pour préparer et exécuter la requête (pour se conformer au principe DRY ).

De cette façon, vous pourrez efficacement:

  • utilisez une seule variable dans votre méthode d'assistance pour identifier la requête du contexte actuel,
  • besoin de taper moins de code à chaque fois que vous souhaitez réexécuter une requête,
  • rendre votre code plus lisible pour les autres.

Exemples:

Considérez ceci, tiré de votre exemple, où la version refactorisée est évidemment meilleure. Bien sûr, votre extrait de code n'était qu'un exemple aux fins de cette question, mais le concept reste vrai et évolue.

Votre exemple 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 ;

Votre exemple 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 ;

Exemple 3 (pseudo-code refactorisé):

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 ...');

L'avantage se manifeste par une réutilisation régulière.

Anecdote personnelle

J'ai d'abord commencé en tant que programmeur C travaillant avec un espace d'écran limité, donc la réutilisation des variables était logique à la fois pour le code compilé (à l'époque) et pour permettre à plus de code d'être lisible à la fois.

Cependant, après avoir évolué vers des langages de niveau supérieur et perfectionné la programmation fonctionnelle, j'ai pris l'habitude d'utiliser des variables immuables et des références immuables autant que possible pour limiter les effets secondaires.

Qu'est-ce qu'il y a pour moi?

Si vous prenez l'habitude d'avoir toutes les entrées de votre fonction immuables et de retourner un nouveau résultat (comme le ferait une vraie fonction mathématique), vous avez l'habitude de ne pas dupliquer les magasins.

Par extension, cela conduit à:

  • vous écrivez de courtes fonctions,
  • avec des objectifs bien définis,
  • qui sont plus faciles à comprendre,
  • réutilliser,
  • d'étendre (que ce soit par héritage OO ou par chaînage fonctionnel),
  • et documenter (comme déjà auto-documenté).

Je ne dis pas qu'il n'y a aucun avantage à l'état mutable ici, je souligne simplement comment l'habitude pourrait se développer sur vous et comment elle influe sur la lisibilité du code.

haylem
la source
2

En termes de conception de code

En général, il est correct de réutiliser des variables pour stocker des valeurs différentes - après tout, c'est pourquoi elles sont appelées variables, car la valeur qui y est stockée varie - tant que la valeur n'est pas seulement du même type mais signifie également la même chose . Par exemple, bien sûr, vous pouvez réutiliser la currentQueryvariable ici:

for currentQuery in queries:
    execute query;

Naturellement, il y a une boucle, vous devez donc réutiliser une variable, mais même s'il n'y avait pas de boucle, cela aurait été correct. Si la valeur ne signifie pas la même chose, utilisez une variable distincte.

Plus précisément, cependant, le code que vous décrivez ne semble pas très bon - il se répète . Il est préférable d'utiliser une boucle ou des appels de méthode d'assistance (ou les deux). Personnellement, j'ai très rarement vu du code de production qui ressemble à votre 1ère ou 2ème version, mais dans les cas que j'ai, je pense que la 2ème version (réutilisation variable) était plus courante.

En termes de performances

Cela dépend du langage, du ou des compilateurs et du ou des systèmes d'exécution utilisés, mais en général, il ne devrait pas y avoir de différence - en particulier, les compilateurs pour les machines de registre basées sur la pile (comme les populaires x86 / x86-64) utilisez la mémoire de pile libre ou enregistrez-la comme cible d'affectation, en ignorant complètement si vous vouliez la même variable ou non.

Par exemple, gcc -O2génère exactement le même binaire, et la seule différence de performances que je connaisse est la taille de la table des symboles lors de la compilation - complètement négligeable, sauf si vous remontez dans le temps dans les années 60.

Un compilateur Java générera du bytecode qui a besoin de plus de stockage pour la 1ère version, mais la gigue de la JVM le supprimera de toute façon, donc encore une fois, je soupçonne qu'il n'y aurait pratiquement aucun impact perceptible sur les performances même si vous avez besoin d'un code hautement optimisé.

Chêne
la source
0

Je pense que la réutilisation de la variable est très bien la plupart du temps.

Pour moi, je réutilise simplement la variable de requête la plupart du temps. J'exécute presque toujours la requête juste après. Lorsque je n'exécute pas la requête immédiatement, j'utilise généralement un nom de variable différent.

Echo dit de réintégrer Monica
la source
-1

Cela peut augmenter l'utilisation de la pile si votre compilateur est particulièrement stupide. Personnellement, je ne pense pas qu'avoir une variable distincte pour chaque requête augmente la lisibilité, vous devez toujours regarder la chaîne de requête pour voir ce qu'elle fait.

James
la source
Je viens de fournir un exemple simple afin qu'il soit plus facile pour les lecteurs de comprendre ce que je recherche. Mon code est beaucoup plus complexe que cela.
Shirish11
-2

Dans l'exemple, j'irais avec le deuxième exemple. Le lecteur et les optimiseurs savent très bien ce que vous faites. Le premier exemple est un peu plus approprié, et avec du code un peu plus compliqué, je l'utiliserais, mais faites-le comme:

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

(À ce stade, je pourrais envisager la solution de tdammers .)

Le problème avec le premier exemple est qu'il querycreest à la portée de l'ensemble du bloc, qui peut être étendu. Cela peut dérouter quelqu'un qui lit le code. Il peut également confondre les optimiseurs, qui peuvent laisser une écriture mémoire inutile et querycresont donc disponibles plus tard si nécessaire (ce qui n'est pas le cas). Avec tous les accolades, queryest stocké uniquement dans un registre, si cela.

Avec des expressions comme "Créer une table" et "exécuter", il ne me semble pas qu'une écriture de mémoire supplémentaire va être remarquée ici, donc je ne ferais que reprocher au code de dérouter le lecteur. Mais il est utile d'être au courant de ce si vous écrivez du code où la vitesse ne importe.

RalphChapin
la source
Je ne suis pas d'accord. Si vous préférez le deuxième exemple pour plus de clarté, il doit être refactorisé en appels successifs à une méthode d'assistance. il donnerait plus de sens et nécessiterait moins de code.
haylem
@haylem: Dans un cas très simple, comme celui-ci, vous ajoutez une méthode d'aide, que quelqu'un qui lit le code doit aller trouver. (Et quelqu'un pourrait avoir des problèmes avec la méthode d'assistance et devoir trouver tous les endroits d'où elle est appelée.) Moins de clarté, à peu près la même quantité de code. Dans un cas plus compliqué, j'irais avec ma solution, puis avec celle de tdammer . J'ai répondu à cette question principalement pour souligner les problèmes (certes obscurs, mais intéressants) que les variables sous-utilisées posent aux humains et aux optimiseurs.
RalphChapin
@haylem: vous et tdammer donnez tous les deux la bonne solution. Je pense simplement que cela peut être exagéré dans certains cas.
RalphChapin