web-dev-qa-db-fra.com

Refactoring: remplacement d'une variable temporaire avec une requête. Est-ce que ce code est un exemple de mauvaise pratique?

Avant de poser ma question, je veux noter que ce n'est pas mon propre code; Il vient du site Web refactoring gouro site:


enter image description here


Je me trouve souvent en utilisant le code à gauche et j'ai une motivation principale pour le faire: dans le code à droite, vous poussez inutilement et évacuez un cadre de pile pour la même opération deux fois de suite. Par exemple, supposons que le prix de base soit en réalité supérieur à 1000. En testant ce conditionnel, vous avez déjà calculé ce que le prix de base est ... Donc, au moment où vous arrivez au corps de ce conditionnel, vous connaissez déjà la valeur, Et il n'est vraiment pas question de la ré-calculer avec un deuxième appel à basePrice(). Dans les deux cas, lorsque le prix de base dépasse 1000 ou est inférieur ou égal à 1000, vous vous retrouvez avec un appel supplémentaire redondant à basePrice().

Question : Le code à droite offre-t-il des avantages réels sur celui de gauche? Le site mentionne que cela permet une plus grande réutilisabilité du code au cas où d'autres méthodes veulent également calculer le prix de base, ce que je suis d'accord avec.

7
AleksandrH

Un refactoring n'est pas une meilleure pratique.

Un refactoring est un moyen de changer de code sans changer de comportement visible de manière externe. Un seul test d'unité bien écrit devrait pouvoir passer à la fois le code de gauche et le bon code sans être touché.

Vous lisez ce message comme si le code à gauche est toujours un problème. Ce n'est pas. Parfois, le code à droite est le problème. Cela vous dit quelque chose que vous pouvez faire si vous voyez le code à gauche comme problème. Cela ne garantit pas que le code à droite n'a pas également de problèmes.

dans le code à droite, vous poussez inutilement et poussez un cadre de pile pour la même opération deux fois de suite.

Vous ne le savez pas réellement.

On dirait que cela devrait l'appeler deux fois mais en fonction de la façon dont il est écrit et le compilateur/interprète utilisé, il pourrait être optimisé.

Même si ce n'est pas le cas, à moins que le calcul ne soit vraiment significatif, l'amélioration de la performance que la mise en cache vous donnerait ne vaut pas la peine de vous inquiéter.

Certains peuvent affirmer que le code à gauche est plus clair. Certains peuvent soutenir que le code à droite fait moins de choses et il est donc préférable de faire une chose.

Je dirai que tous les deux pourraient être améliorés en externalisant la dépendance sur le prix de base et en le faisant un paramètre explicite à transmettre. Ce qui est encore un autre refactoring.

Faire cela est objectivement un refactoring. L'idée qu'il est préférable est une opinion subjective. Lorsque vous faites des choix subjectifs, demandez à votre équipe. Ce sont ceux qui doivent vivre avec elle.

20
candied_orange

Pour moi, en utilisant la variable locale pour capturer un résultat de la requête (la requête de l'état d'objet) dit quelque chose d'important: que je ne fais qu'une requête de ces valeurs de champ (pour calculer une valeur) et, ma logique s'attend à travailler avec cette valeur. Ce que je trouve être claire et simple.

ATTENDU QUE la répétition des accès au champ - que ce soit directement ou par la méthode appel - me dit que ces valeurs peuvent changer et que nous voulons nous assurer de toujours utiliser le dernier.

Malheureusement, dans ce cas, si les champs changent entre la condition IF et la partie, puis, ce code ne le gérera pas vraiment. Pour cette raison, je trouve un tel code déroutant - pourquoi l'auteur a-t-il ressenti la nécessité de répéter la requête si la logique ici ne peut pas gérer les changements dans les champs sous-jacents?

Dans l'ensemble, comme vous pouvez penser @ Robert toujours utiliser une variable locale avec le calcul de la requête dans une méthode refondus. (Et je voudrais b/c de ce qui précède.)

Faire ce refactoring soulève alors la question de savoir pourquoi une multiplication est faite dans sa propre méthode et que les multiplications en pourcentage choisis sur la base de certaines ampleurs ne sont pas. C'est une amélioration en quelque sorte que la méthode calcule maintenant le total actualisé du total de base (calculé ailleurs), c'est-à-dire une règle codée dans le code.

Une prochaine mise à jour logique, en raison du fait que la logique métier est soumis au changement par diverses campagnes et promotions, est qu'une telle règle devrait vraiment être externalisées (remplaçable d'exécution) d'une certaine façon au lieu du disque codé avec si de et constantes dans le code.

7
Erik Eidt

Je suis d'accord avec la réponse de Candied_Orange, mais je serais attentif que le refactorat spécifique mentionné ici pourrait entraîner des bogues et/ou des instabilités difficiles à détecter subtils.

Le refactoring donné ne peut être connu que pour être équivalent si et seulement s'il est garanti que les valeurs de quantity et itemPrice _ ne changeront pas lors de l'exécution de la méthode. S'il y a une chance de pouvoir, utiliser la variable locale n'est pas simplement une "meilleure pratique", elle est requise pour l'exactitude.

Notez que la création de la méthode basePrice() _ et supprimer la variable locale sont deux modifications distinctes. L'introduction de cette méthode n'indique pas l'enlèvement de la variable locale ou vice/versa.

Pourquoi la valeur pourrait-elle changer? Supposons qu'il a déjà été mis en œuvre comme méthode. Savez-vous si les appels répétés à la méthode produisent le même résultat? Peut-être que la méthode basePrice() contient un appel de recherche sur le prix. Vous pourriez vous retrouver avec le basePrice étant plus de 1000 pour la déclaration IF mais moins de 1000 lors du calcul du rabais (ou de l'inverse.) Oups! Les tests unitaires vont-ils attraper ce problème? Sauf si vous avez mis en place un test spécifique de ce scénario: probablement pas.

Ou envisager le cas d'un environnement multi-fileté. Si ces valeurs sont des références à des valeurs pouvant être modifiées par d'autres threads, vous pouvez vous retrouver avec un problème similaire. Encore une fois, difficile à trouver avec des tests unitaires (ou tout autre test.)

Il existe des moyens de résoudre ce par exemple. Faites ces valeurs finales/non modifiables (ce que je recommanderais fortement) Le point ici ne fait pas que les appels ne sont pas toujours faux, mais que vous devez réaliser qu'un tel changement n'est pas intrinsèquement équivalent. Vous devez examiner de tels changements dans le contexte de l'ensemble du programme.

3
JimmyJames

La principale raison pour laquelle j'utiliserais le code droit est que cela permet base de base de test unitaire ().

Est-ce que quelque chose vaut la peine d'être testé? Peut-être; dépend beaucoup des détails spécifiques.

2
Roger

Il s'agit de refactoring, ce qui signifie que la modification du code sans changer sa signification. Vous pouvez refroidir de la gauche à droite ou de droite à gauche, quelle que soit votre préférence.

En fait, nous avons deux refacteurs ici: remplacer une expression avec une fonction et remplacer une variable stockant l'expression avec plusieurs évaluations (de l'expression ou la fonction). Les deux sont parfois bons, parfois mauvais. Plus l'expression complexe et non évidente est, et plus il est utilisé, mieux il devient d'utiliser une fonction. Et plus il est temps d'évaluer, mieux il est préférable de stocker le résultat dans une variable. Il répond également à la question de savoir si les appels de fonction sont idempotents ou peuvent renvoyer différentes valeurs - si votre résultat de la fonction est stocké dans une variable locale, cela ne changera pas. Mais si vous vous attendez à ce qu'il change comme le temps () ou aléatoire (), vous pouvez vouloir plusieurs appels. Ou pas.

1
gnasher729