web-dev-qa-db-fra.com

Nom pour cet antipattern? Champs en tant que variables locales

Dans un code que je passe en revue, je vois des choses qui sont l'équivalent moral des éléments suivants:

public class Foo
{
    private Bar bar;

    public MethodA()
    {
        bar = new Bar();
        bar.A();
        bar = null;
    }

    public MethodB()
    {
        bar = new Bar();
        bar.B();
        bar = null;
    }
}

Le champ bar est ici logiquement une variable locale, car sa valeur n'est jamais destinée à persister entre les appels de méthode. Cependant, comme la plupart des méthodes de Foo nécessitent un objet de type Bar, l'auteur du code d'origine vient de créer un champ de type Bar.

  1. C'est évidemment mauvais, non?
  2. Y a-t-il un nom pour cet anti-modèle?
69
JSBձոգչ

C'est évidemment mauvais, non?

Oui.

  • Cela rend les méthodes non réentrantes, ce qui pose problème si elles sont appelées sur la même instance de manière récursive ou dans un contexte multithread.

  • Cela signifie que l'état d'un appel fuit vers un autre appel (si vous oubliez de réinitialiser).

  • Cela rend le code difficile à comprendre car vous devez vérifier les éléments ci-dessus pour être sûr de ce que le code fait réellement. (Contrairement à l'utilisation de variables locales.)

  • Cela rend chaque instance de Foo plus grande qu'elle ne devrait l'être. (Et imaginez faire cela pour N variables ...)

Y a-t-il un nom pour cet anti-modèle?

OMI, cela ne mérite pas d'être appelé un antipattern. C'est juste une mauvaise habitude de codage/une mauvaise utilisation de Java constructions/code de merde. C'est le genre de chose que vous pourriez voir dans le code d'un étudiant de premier cycle lorsque l'étudiant a sauté des cours et/ou n'a aucune aptitude à la programmation. Si vous le voyez dans le code de production, c'est un signe que vous devez faire beaucoup plus de révisions de code ...


Pour mémoire, la bonne façon d'écrire ceci est:

public class Foo
{
    public methodA()
    {
        Bar bar = new Bar();  // Use a >>local<< variable!!
        bar.a();
    }

    // Or more concisely (in this case) ...
    public methodB()
    {
        new Bar().b();
    }
}

Notez que j'ai également corrigé les noms de méthode et de variable pour se conformer aux règles de style acceptées pour les identificateurs Java.

87
Stephen C

J'appellerais cela une grande portée inutile pour une variable. Ce paramètre autorise également les conditions de concurrence si plusieurs threads accèdent à MethodA et MethodB.

39
Th 00 mÄ s

Il s'agit d'un cas spécifique d'un modèle général appelé global doorknobbing. Lors de la construction d'une maison, il est tentant d'acheter une seule poignée de porte et de la laisser traîner. Quand quelqu'un veut utiliser une porte ou une armoire, il suffit de saisir cette poignée de porte globale. Si les poignées de porte sont chères, cela peut être une bonne conception.

Malheureusement, lorsque votre ami arrive et que la poignée de porte est utilisée simultanément à deux endroits différents, la réalité éclate. Si la poignée de porte est si chère que vous ne pouvez en acheter qu'une, alors cela vaut la peine de construire un système qui permet aux gens d'attendre leur tour en toute sécurité.

Dans ce cas, la poignée de porte n'est qu'une référence, elle est donc bon marché. Si la poignée de porte était un objet réel (et qu'ils réutilisaient l'objet au lieu de simplement la référence), alors cela pourrait être assez cher pour être un prudent global doorknob. Cependant, lorsqu'il est bon marché, il est connu sous le nom de cheapskate global doorknob.

Votre exemple est de la variété cheapskate.

30
Ned Twigg

La principale préoccupation ici serait la concurrence - Si le Foo est utilisé par plusieurs threads, vous avez un problème.

Au-delà, c'est juste idiot - les variables locales gèrent parfaitement leur propre cycle de vie. Les remplacer par des variables d'instance qui doivent être annulées lorsqu'elles ne sont plus utiles est une invitation à l'erreur.

19
Matthew Flynn

C'est un cas spécifique de "mauvaise portée", avec un côté de "réutilisation variable".

15
ikegami

Ce n'est pas un anti-modèle. Les anti-modèles ont une propriété qui donne l'impression que c'est une bonne idée, ce qui conduit les gens à le faire exprès; ils sont planifiés comme des modèles, puis ça va terriblement mal.

C'est aussi ce qui fait débat pour savoir si quelque chose est un modèle, un anti-modèle ou un modèle couramment mal appliqué qui a encore des utilisations à certains endroits.

C'est tout simplement faux.

Pour en ajouter un peu plus.

Ce code est superstitieux, ou au mieux une pratique culte du fret.

Une superstition est quelque chose qui se fait sans justification claire. Cela peut être lié à quelque chose de réel, mais la connexion n'est pas logique.

Une pratique de culte du fret est une pratique où vous essayez de copier quelque chose que vous avez appris d'une source plus compétente, mais vous copiez en fait les artefacts de surface plutôt que le processus (ainsi nommé pour un culte en Papouasie-Nouvelle-Guinée qui ferait radios de contrôle des avions en bambou dans l'espoir de faire revenir les avions japonais et américains de la Seconde Guerre mondiale).

Dans ces deux cas, il n'y a pas de véritable argument à faire.

Un anti-modèle est une tentative d'amélioration raisonnable, que ce soit dans le petit (cette ramification supplémentaire pour traiter ce cas supplémentaire qui doit être traité, qui conduit à du code spaghetti) ou dans le grand où vous implémentez très délibérément un modèle qui soit est discrédité soit débattu (beaucoup décriraient les singletons comme tels, certains excluant un écriture seule - par exemple les objets de journalisation ou en lecture seule par exemple les objets de paramètres de configuration - et certains condamneraient même ceux-ci) ou bien où vous résolvez le mauvais problème (lorsque .NET a été publié pour la première fois, MS a recommandé un modèle pour traiter la suppression lorsque vous aviez à la fois des champs non gérés et des champs gérés jetables - cela résout en effet très bien cette situation, mais le vrai problème est que vous avez les deux types de champ dans la même classe).

En tant que tel, un anti-modèle est quelque chose qu'une personne intelligente qui connaît bien la langue, le domaine problématique et les bibliothèques disponibles fera délibérément, qui a toujours (ou prétendrait avoir) un inconvénient qui l'emporte sur la hausse.

Étant donné qu'aucun de nous ne commence à bien connaître une langue, un domaine problématique et les bibliothèques disponibles, et que tout le monde peut manquer quelque chose en passant d'une solution raisonnable à une autre (par exemple, commencer à stocker quelque chose dans un champ pour une bonne utilisation, puis essayer de refactorisez-le mais ne terminez pas le travail, et vous vous retrouverez avec du code comme dans la question), et comme nous manquons tous de temps en temps dans l'apprentissage, nous avons tous créé un code superstitieux ou culte à un moment donné . La bonne chose, c'est qu'ils sont plus clairs à identifier et à corriger que les anti-modèles. Les vrais anti-modèles ne sont sans doute pas des anti-modèles, ou ont une certaine qualité attrayante, ou au moins ont un moyen d'en attirer un même lorsqu'ils sont identifiés comme mauvais (trop et trop peu de couches sont toutes deux mauvaises sans controverse, mais en évitant une mène à l'autre).

8
Jon Hanna

(1) Je dirais que ce n'est pas bon.

Il effectue un nettoyage manuel que la pile peut faire automatiquement avec des variables locales.

Il n'y a aucun avantage en termes de performances car "nouveau" est appelé à chaque appel de méthode.

EDIT: l'empreinte mémoire de la classe est plus grande car le pointeur de barre occupe toujours de la mémoire pendant la durée de vie de la classe. Si le pointeur de la barre était local, il n'utiliserait de la mémoire que pour la durée de vie de l'appel de méthode. La mémoire du pointeur n'est qu'une goutte dans l'océan, mais c'est toujours une goutte inutile.

La barre est visible pour les autres méthodes de la classe. Les méthodes qui n'utilisent pas la barre ne devraient pas pouvoir le voir.

Erreurs basées sur l'état. Dans ce cas particulier, les erreurs de base d'état ne doivent se produire que dans le multithreading puisque "new" est appelé à chaque fois.

(2) Je ne sais pas s'il y a un nom car c'est en fait plusieurs problèmes, pas un.
- Ménage manuel quand automatique est disponible
- état inutile
- déclare que la vie dure plus longtemps que sa vie
- la visibilité ou la portée est trop élevée

3
mike30

Aller à contre-courant ici, mais bien que je ne considère pas que l'exemple donné soit un bon style de codage tel qu'il est dans sa forme actuelle, le modèle existe tout en faire des tests unitaires.

Cette façon assez standard de faire des tests unitaires

public class BarTester
{
    private Bar bar;

    public Setup() { bar = new Bar(); }
    public Teardown() { bar = null; }

    public TestMethodA()
    {
        bar.A();        
    }

    public TestMethodB()
    {
        bar.B();
    }
}

est juste une refactorisation de cet équivalent du code OP

public class BarTester
{
    private Bar bar;

    public TestMethodA()
    {
        bar = new Bar();
        bar.A();
        bar = null;
    }

    public TestMethodB()
    {
        bar = new Bar();
        bar.B();
        bar = null;
    }
}

Je n'écrirais jamais de code comme donné par l'exemple de OP, cela crie le refactoring, mais je considère que le pattern n'est ni invalide ni antipattern.

2

Je l'appellerais le "Xénophobe errant". Il veut être laissé seul mais finit par ne pas savoir exactement où ni ce que c'est. C'est donc une mauvaise chose comme d'autres l'ont dit.

2
World Engineer

Cette odeur de code est appelée champ temporaire dans la refactorisation par Beck/Fowler.

http://sourcemaking.com/refactoring/temporary-field

Modifié pour ajouter plus d'explications à la demande des modérateurs: vous pouvez en savoir plus sur l'odeur du code dans l'URL référencée ci-dessus ou dans la copie papier du livre. Étant donné que le PO recherchait le nom de cette odeur particulière d'antipattern/code, je pensais qu'il serait utile de citer une référence jusqu'ici non mentionnée à partir d'une source établie de plus de 10 ans, bien que je réalise pleinement que je suis en retard jeu sur cette question, il est donc peu probable que la réponse soit votée ou acceptée.

Si ce n'est pas trop promotionnel personnellement, je fais également référence et explique cette odeur de code particulière dans mon prochain cours Pluralsight, Refactoring Fundamentals, qui devrait être publié en août 2013.

Pour ajouter de la valeur, parlons de la façon de résoudre ce problème particulier. Il existe plusieurs options. Si le champ n'est vraiment utilisé que par une seule méthode, il peut être rétrogradé en variable locale. Cependant, si plusieurs méthodes utilisent le champ pour communiquer (au lieu de paramètres), c'est le cas classique décrit dans Refactoring, et peut être corrigé en remplaçant le champ par des paramètres, ou si les méthodes qui fonctionnent avec ce code sont étroitement liée, Extraire la classe peut être utilisée pour extraire les méthodes et les champs dans leur propre classe, dans laquelle le champ est toujours dans un état valide (peut-être défini lors de la construction) et représente l'état de cette nouvelle classe. Si vous suivez la route des paramètres, mais qu'il y a trop de valeurs à transmettre, une autre option consiste à introduire un nouvel objet de paramètre, qui peut être passé à la place de toutes les variables individuelles.

2
ssmith

Cela me semble être une horrible mauvaise application du modèle Flyweight. J'ai malheureusement connu quelques développeurs qui doivent utiliser la même "chose" plusieurs fois dans différentes méthodes et la retirer simplement dans un champ privé dans une tentative malavisée d'économiser de la mémoire ou d'améliorer les performances ou une autre pseudo-optimisation bizarre. Dans leur esprit, ils essaient de résoudre un problème similaire, car Flyweight est censé résoudre uniquement s'ils le font dans une situation où ce n'est pas réellement un problème qui doit être résolu.

1
Evicatos

Je dirais que lorsque vous allez droit au but, c'est vraiment un manque de cohésion, et je pourrais lui donner le nom de "fausse cohésion". J'inventerais (ou si je l'obtiens de quelque part et l'oublierais, "emprunterais") ce terme parce que la classe semble être cohérente dans la mesure où ses méthodes semblent fonctionner sur un champ membre. Cependant, en réalité, ce n'est pas le cas, ce qui signifie que la cohésion apparente est en fait fausse.

Quant à savoir si c'est mauvais ou pas, je dirais que c'est clairement le cas, et je pense que Stephen C fait un excellent travail pour expliquer pourquoi. Cependant, qu'il mérite d'être appelé "anti-modèle" ou non, je pense que cela et tout autre paradigme de codage pourraient faire avec une étiquette, car cela facilite généralement la communication.

1
Erik Dietrich

Mis à part les problèmes déjà mentionnés, l'utilisation de cette approche dans un environnement sans collecte automatique de déchets (par exemple C++) entraînerait des fuites de mémoire, car les objets temporaires ne sont pas libérés après utilisation. (Bien que cela puisse sembler un peu tiré par les cheveux, il y a des gens qui changeraient simplement 'null' en 'NULL' et seraient heureux que le code se compile.)

1
peterph