web-dev-qa-db-fra.com

Faut-il éliminer les variables locales si nous le pouvons?

Par exemple, pour garder un processeur sous Android, je peux utiliser du code comme ceci:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc");
wakeLock.acquire();

mais je pense que les variables locales powerManager et wakeLock peuvent être éliminées:

((PowerManager)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag")
    .acquire();

une scène similaire apparaît dans la vue des alertes iOS, par exemple: à partir de

UIAlertView *alert = [[UIAlertView alloc]
    initWithTitle:@"my title"
    message:@"my message"
    delegate:nil
    cancelButtonTitle:@"ok"
    otherButtonTitles:nil];
[alert show];

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    [alertView release];
}

à:

[[[UIAlertView alloc]
    initWithTitle:@"my title"
    message:@"my message"
    delegate:nil
    cancelButtonTitle:@"ok"
    otherButtonTitles:nil] show];

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    [alertView release];
}

Est-ce une bonne pratique d'éliminer une variable locale si elle n'est utilisée qu'une seule fois dans la portée?

101
ggrr

Le code est lu beaucoup plus souvent qu'il n'est écrit, vous devez donc avoir pitié de la pauvre âme qui devra lire le code dans six mois (ce peut être vous) et vous efforcer d'obtenir le code le plus clair et le plus facile à comprendre. À mon avis, la première forme, avec des variables locales, est beaucoup plus compréhensible. Je vois trois actions sur trois lignes, plutôt que trois actions sur une seule ligne.

Et si vous pensez que vous optimisez quelque chose en vous débarrassant des variables locales, vous ne l'êtes pas. Un compilateur moderne mettra powerManager dans un registre 1, qu'une variable locale soit utilisée ou non, pour appeler la méthode newWakeLock. Il en va de même pour wakeLock. Vous vous retrouvez donc avec le même code compilé dans les deux cas.

1 Si vous aviez beaucoup de code intermédiaire entre la déclaration et l'utilisation de la variable locale, cela pourrait aller sur la pile, mais c'est un détail mineur.

237
Tony BenBrahim

Certains commentaires très positifs l'ont dit, mais aucune des réponses que j'ai vues ne l'a fait, je vais donc l'ajouter comme réponse. Votre principal facteur pour décider de ce problème est:

Débogage

Souvent, les développeurs passent beaucoup plus de temps et d'efforts à déboguer qu'à écrire du code.

Avec variables locales, vous pouvez:

  • Point d'arrêt sur la ligne où il est affecté (avec toutes les autres décorations de point d'arrêt, comme le point d'arrêt conditionnel, etc ...)

  • Inspecter/regarder/imprimer/changer la valeur de la variable locale

  • Problèmes de capture qui surviennent en raison de lancers.

  • Avoir des traces de pile lisibles (la ligne XYZ a une opération au lieu de 10)

Sans variables locales, toutes les tâches ci-dessus sont soit beaucoup plus difficiles, extrêmement difficiles ou carrément impossibles, selon votre débogueur.

En tant que tel, suivez la maxime infâme (écrivez le code d'une manière que vous feriez comme si le prochain développeur à le maintenir après vous-même était un fou fou qui sait où vous vivez), et péchez du côté de débogage plus facile, ce qui signifie tiliser des variables locales.

98
DVK

Seulement si cela rend le code plus facile à comprendre. Dans vos exemples, je pense que cela rend la lecture plus difficile.

L'élimination des variables dans le code compilé est une opération triviale pour tout compilateur respectable. Vous pouvez inspecter la sortie vous-même pour vérifier.

47
whatsisname

Votre question "est-ce une bonne pratique d'éliminer une variable locale si elle n'est utilisée qu'une seule fois dans la portée?" teste les mauvais critères. L'utilité d'une variable locale ne dépend pas du nombre de fois où elle est utilisée mais de la clarté du code. L'étiquetage de valeurs intermédiaires avec des noms significatifs peut améliorer la clarté dans des situations telles que celle que vous présentez, car il décompose le code en morceaux plus petits et plus digestes.

À mon avis, le code non modifié que vous avez présenté est plus clair que votre code modifié et doit donc rester inchangé. En fait, j'envisagerais de retirer une variable locale de votre code modifié afin d'améliorer la clarté.

Je ne m'attendrais pas à un impact sur les performances des variables locales, et même s'il y en a une, elle sera probablement trop petite pour être considérée à moins que le code ne soit dans une boucle très serrée dans une partie critique de la vitesse du programme.

29
Jack Aidley

Peut être.

Personnellement, j'hésiterais à éliminer les variables locales si un transtypage est impliqué. Je trouve votre version compacte moins lisible car le nombre de crochets commence à approcher une limite mentale que j'ai. Il introduit même un nouvel ensemble de crochets qui n'était pas nécessaire dans la version légèrement plus détaillée utilisant une variable locale.

La mise en évidence de la syntaxe fournie par l'éditeur de code peut atténuer ces problèmes dans une certaine mesure.

À mes yeux, c'est le meilleur compromis:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc").acquire();

Gardant ainsi une variable locale et abandonnant l'autre. En C #, j'utiliserais le mot-clé var sur la première ligne car le transtypage fournit déjà les informations de type.

15
9Rune5

Bien que j'accepte la validité des réponses favorisant les variables locales, je vais jouer l'avocat du diable et présenter une vision contraire.

Personnellement, je suis quelque peu contre l'utilisation de variables locales uniquement à des fins de documentation, bien que cette raison elle-même indique que la pratique a une valeur: les variables locales pseudo-documentent effectivement le code.

Dans votre cas, le problème principal est l'indentation et non l'absence de variables. Vous pouvez (et devez) formater le code comme suit:

((PowerManager)getSystemService(POWER_SERVICE))
        .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag")
        .acquire();

Comparez cela avec la version avec des variables locales:

PowerManager powerManager=(PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"abc");
wakeLock.acquire();

Avec les variables locales: les noms ajoutent des informations au lecteur et les types sont clairement spécifiés, mais les appels de méthode réels sont moins visibles et (à mon avis) il ne se lit pas aussi clairement.

Sans utiliser de variables locales: c'est plus concis et les appels de méthode sont plus visibles, mais vous pourriez demander plus d'informations sur ce qui est retourné. Certains IDE peuvent cependant vous le montrer.

Trois autres commentaires:

  1. À mon avis, l'ajout de code qui n'a pas d'utilisation fonctionnelle peut parfois prêter à confusion car le lecteur doit se rendre compte qu'il n'a en effet aucune utilisation fonctionnelle et qu'il est simplement là pour la "documentation". Par exemple, si cette méthode comportait 100 lignes, il ne serait pas évident que les variables locales (et donc le résultat de l'appel de méthode) n'étaient pas requises ultérieurement, à moins que vous ne lisiez la méthode entière.

  2. L'ajout des variables locales signifie que vous devez spécifier leurs types, ce qui introduit une dépendance dans le code qui autrement ne serait pas là. Si le type de retour des méthodes changeait trivialement (par exemple, il a été renommé), vous devrez mettre à jour votre code, alors que sans les variables locales, vous ne le feriez pas.

  3. Le débogage pourrait être plus facile avec les variables locales si votre débogueur n'affiche pas les valeurs renvoyées par les méthodes. Mais la solution consiste à corriger les lacunes des débogueurs et non à modifier le code.

13
rghome

Il y a une tension de motivations ici. L'introduction de variables temporaires peut faciliter la lecture du code. Mais ils peuvent également empêcher, et rendre plus difficile à voir, d'autres refactorings possibles, tels que Extract Method , et Replace Temp with Query . Ces derniers types de refactorings, le cas échéant, offrent souvent des avantages beaucoup plus importants que ne le ferait la variable temp.

Sur les motivations de ces derniers refactorings, Fowler écrit :

"Le problème avec les temps est qu'ils sont temporaires et locaux. Parce qu'ils ne peuvent être vus que dans le contexte de la méthode dans laquelle ils sont utilisés, les temps ont tendance à encourager les méthodes plus longues, parce que c'est la seule façon d'atteindre la température. Par en remplaçant le temp par une méthode de requête, n'importe quelle méthode de la classe peut obtenir les informations. Cela aide beaucoup à trouver un code plus propre pour la classe. "

Donc, oui, utilisez des temps s'ils rendent le code plus lisible, surtout si c'est une norme locale pour vous et votre équipe. Mais sachez que cela peut parfois rendre plus difficile de repérer de plus grandes améliorations alternatives. Si vous pouvez développer votre capacité à ressentir quand cela vaut la peine de vous passer de ces températures et à devenir plus à l'aise en le faisant, alors c'est probablement une bonne chose.

FWIW J'ai personnellement évité de lire le livre de Fowler "Refactoring" pendant dix ans parce que j'imaginais qu'il n'y avait pas grand-chose à dire sur des sujets aussi simples. J'avais complètement tort. Quand je l'ai finalement lu, cela a changé mes pratiques de codage quotidiennes, beaucoup pour le mieux.

6
Jonathan Hartley

Eh bien, c'est une bonne idée d'éliminer les variables locales si vous le pouvez rendre ainsi le code plus lisible. Cette longue ligne unique n'est pas lisible, mais elle suit un style assez verbeux OO. Si vous pouviez le réduire à quelque chose comme

acquireWakeLock(POWER_SERVICE, PARTIAL, "abc");

alors je dirais que ce serait probablement une bonne idée. Vous pouvez peut-être introduire des méthodes d'assistance pour le résumer à quelque chose de similaire; si un code comme celui-ci apparaît plusieurs fois, cela pourrait valoir la peine.

3
leftaroundabout

C'est même un contre-modèle avec son propre nom: Train Wreck . Plusieurs raisons d'éviter le remplacement ont déjà été évoquées:

  • Plus difficile à lire
  • Plus difficile à déboguer (à la fois pour surveiller les variables et détecter les emplacements des exceptions)
  • Violation de la loi de Déméter (LoD)

Considérez si cette méthode en sait trop sur d'autres objets. Chaînage de méthodes est une alternative qui pourrait vous aider à réduire le couplage.

N'oubliez pas non plus que les références aux objets sont vraiment bon marché.

2
Borjab

Une chose à noter est que le code est lu fréquemment, à différentes "profondeurs". Ce code:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc");
wakeLock.acquire();

est facile à "survoler". Il s'agit de 3 déclarations. Nous arrivons d'abord avec un PowerManager. Ensuite, nous arrivons avec un WakeLock. Ensuite, nous acquire le wakeLock. Je peux voir cela très facilement simplement en regardant le début de chaque ligne; les affectations de variables simples sont vraiment faciles à reconnaître partiellement comme "Type varName = ..." et survolent mentalement le "...". De même, la dernière déclaration n'est évidemment pas la forme de la cession, mais elle ne comporte que deux noms, de sorte que le "principal Gist" est immédiatement apparent. Souvent, c'est tout ce que je devrais savoir si j'essayais simplement de répondre "que fait ce code?" à un niveau élevé.

Si je suis en train de chasser un bug subtil qui, je pense, est là, alors, évidemment, je devrai y revenir plus en détail, et je me souviendrai des "...". Mais la structure des instructions distinctes m'aide toujours à faire une seule instruction à la fois (particulièrement utile si j'ai besoin de plonger plus profondément dans la mise en œuvre des choses appelées dans chaque instruction; quand je reviens, j'ai bien compris "une unité" et peut ensuite passer à la déclaration suivante).

((PowerManager)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag")
    .acquire();

Maintenant, c'est tout une déclaration. La structure de niveau supérieur n'est pas si facile à lire; dans la version originale de l'OP, sans sauts de ligne et indentation pour communiquer visuellement n'importe quelle structure, j'aurais dû compter les parenthèses pour le décoder en une séquence de 3 étapes. Si certaines des expressions en plusieurs parties sont imbriquées les unes dans les autres plutôt que organisées comme une chaîne d'appels de méthode, alors cela pourrait toujours ressembler à ceci, donc je dois faire attention à ne pas le faire sans compter les parenthèses. Et si je fais confiance à l'indentation et que je passe simplement à la dernière chose comme point présumé de tout cela, que me dit .acquire() seul?

Parfois, cependant, c'est ce que vous voulez. Si j'applique votre transformation à mi-chemin et que j'écris:

WakeLock wakeLock =
     ((PowerManeger)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLockTage");
wakeLock.acquire();

Maintenant, cela communique à un survol rapide "obtenir un WakeLock, puis acquire it". Encore plus simple que la première version. Il est immédiatement évident que la chose acquise est un WakeLock. Si obtenir le PowerManager est juste un sous-détail qui est assez insignifiant au point de ce code, mais le wakeLock est important, alors cela pourrait réellement aider à enterrer le PowerManager il est donc naturel de survoler si vous essayez simplement de vous faire une idée rapide de ce que fait ce code. Et ne pas le nommer indique qu'il est utilisé qu'une seule fois, et parfois c'est ce qui est important (si le reste de la portée est très longtemps, je devrais lire tout cela pour savoir s'il est utilisé à nouveau; bien que l'utilisation de sous-étendues explicites puisse être une autre façon de résoudre ce problème, si votre langue les prend en charge).

Ce qui montre que tout dépend du contexte et de ce que vous souhaitez communiquer. Comme pour l'écriture de prose en langage naturel, il existe toujours de nombreuses façons d'écrire un morceau de code donné qui sont fondamentalement équivalents en termes de contenu d'information. Comme pour l'écriture de prose en langage naturel, la façon dont vous choisissez entre les deux devrait généralement pas être d'appliquer des règles mécanistes comme "éliminer toute variable locale qui ne se produit qu'une seule fois". Plutôt la façon dont vous choisissez d'écrire votre code mettra l'accent sur certaines choses et en atténuera d'autres. Vous devez vous efforcer de faire ces choix consciemment (y compris le choix d'écrire du code moins lisible pour des raisons techniques parfois), en fonction de ce que vous voulez vraiment souligner. Pensez en particulier à ce qui servira aux lecteurs qui ont juste besoin de "comprendre l'essentiel" de votre code (à différents niveaux), car cela se produira beaucoup plus souvent qu'une lecture très proche expression par expression.

2
Ben

Considérons la loi de Déméter ici. Dans l'article Wikipedia sur LoD, ce qui suit est indiqué:

La loi de Déméter pour les fonctions exige qu'une méthode m d'un objet O ne puisse invoquer que les méthodes des types d'objets suivants: [2]

  1. O lui-même
  2. paramètres de m
  3. Tous les objets créés/instanciés dans m
  4. Objets composants directs d'O
  5. Une variable globale, accessible par O, dans le cadre de m

L'une des conséquences du respect de cette loi est que vous devez éviter d'appeler des méthodes d'objets renvoyés par d'autres méthodes dans une longue chaîne pointillée, comme le montre le deuxième exemple ci-dessus:

((PowerManager)getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag").acquire();

Il est vraiment difficile de comprendre ce que cela fait. Pour le comprendre, vous devez comprendre ce que fait chaque procédure, puis déchiffrer quelle fonction est appelée sur quel objet. Le premier bloc de code

PowerManager powerManager=(PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock=powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"abc");
wakeLock.acquire();

montre clairement ce que vous essayez de faire - vous obtenez un objet PowerManager, obtenez un objet WakeLock à partir du PowerManager, puis acquérez le wakeLock. Ce qui précède suit la règle n ° 3 de LoD - vous instanciez ces objets dans votre code, et pouvez donc les utiliser pour faire ce dont vous avez besoin.

Une autre façon de penser est peut-être de se rappeler que lors de la création d'un logiciel, vous devez écrire pour plus de clarté, pas pour être bref. 90% de tout le développement de logiciels est de maintenance. N'écrivez jamais un morceau de code que vous ne seriez pas heureux de maintenir.

Bonne chance.

Un aspect important n'a pas été mentionné dans les autres réponses: chaque fois que vous ajoutez une variable, vous introduisez un état mutable. C'est généralement une mauvaise chose car cela rend votre code plus complexe et donc plus difficile à comprendre et à tester. Bien sûr, plus la portée de la variable est petite, plus le problème est petit.

Ce que vous voulez, ce n'est pas une variable dont la valeur peut être modifiée mais une constante temporaire. Pensez donc à l'exprimer dans votre code si votre langue le permet. En Java vous pouvez utiliser final et en C++ vous pouvez utiliser const. Dans la plupart des langages fonctionnels, c'est le comportement par défaut.

Il est vrai que les variables locales sont le type d'état mutable le moins nuisible. Les variables membres peuvent causer beaucoup plus de problèmes et les variables statiques sont encore pires. Je trouve néanmoins important d'exprimer aussi précisément que possible dans votre code ce qu'il est censé faire. Et il y a une énorme différence entre une variable qui pourrait être modifiée plus tard et un simple nom pour un résultat intermédiaire. Donc, si votre langue vous permet d'exprimer cette différence, faites-le.

1
Frank Puffer

La question posée est "Devrions-nous éliminer les variables locales si nous le pouvons"?

Non, vous ne devez pas éliminer simplement parce que vous le pouvez.

Vous devez suivre les directives de codage dans votre boutique.

Pour la plupart, les variables locales facilitent la lecture et le débogage du code.

J'aime ce que tu as fait avec PowerManager powerManager. Pour moi, une minuscule de la classe signifie que c'est juste une utilisation unique.

Ce que vous ne devriez pas faire, c'est si la variable va conserver des ressources coûteuses. De nombreuses langues ont une syntaxe pour une variable locale qui doit être nettoyée/publiée. En C #, il utilise.

using(SQLconnection conn = new SQLconnnection())
{
    using(SQLcommand cmd = SQLconnnection.CreateCommand())
    {
    }
}
1
paparazzo