web-dev-qa-db-fra.com

Passer la variable membre en tant que paramètre de méthode

Dans un projet, j'ai trouvé un code comme celui-ci:

class SomeClass
{
    private SomeType _someField;

    public SomeType SomeField
    {
        get { return _someField; }
        set { _someField = value; }
    }

    protected virtual void SomeMethod(/*...., */SomeType someVar)
    {
    }

    private void SomeAnotherMethod()
    {
        //.............
        SomeMethod(_someField);
        //.............
    }

};

Comment convaincre mes coéquipiers que c'est du mauvais code?

Je pense que c'est une complication inutile. Pourquoi passer une variable membre comme paramètre de méthode si vous y avez déjà accès? Il s'agit également d'une violation de l'encapsulation.

Voyez-vous d'autres problèmes avec ce code?

33
tika

Je pense que c'est un sujet valable, mais la raison pour laquelle vous obtenez des réponses mitigées est à cause de la façon dont la question a été formulée. Personnellement, j'ai eu les mêmes expériences avec mon équipe où passer des membres comme arguments n'était pas nécessaire et alourdir le code. Nous aurions une classe qui fonctionne avec un ensemble de membres mais certaines fonctions accèdent directement aux membres et d'autres fonctions modifieraient les mêmes membres via des paramètres (c'est-à-dire utiliser des noms complètement différents) et il n'y avait absolument aucune raison technique de le faire. Par raison technique, je veux dire un exemple que Kate a fourni.

Je recommanderais de prendre du recul et au lieu de se concentrer exactement sur le passage des paramètres par les membres, engager des discussions avec votre équipe sur la clarté et la lisibilité. Plus formellement ou simplement dans les couloirs, discutez de ce qui rend certains segments de code plus faciles à lire et d'autres segments de code plus difficiles. Ensuite, identifiez les mesures de qualité ou les attributs de code propre qui, en tant qu'équipe, vous voudriez viser. Après tout, même lorsque nous travaillons sur des projets écologiques, nous passons plus de 90% du temps à lire et dès que le code est écrit (disons 10-15 minutes plus tard), il passe en maintenance où la lisibilité est encore plus importante.

Donc, pour votre exemple spécifique ici, l'argument que j'utiliserais est que moins de code est toujours plus facile à lire que plus de code. Une fonction qui a 3 paramètres est plus difficile à traiter pour le cerveau qu'une fonction qui n'a aucun ou 1 paramètre. S'il y a un autre nom de variable, le cerveau doit garder une trace d'une autre chose lors de la lecture du code. Donc, pour se souvenir de "int m_value" puis de "int localValue" et rappelez-vous que l'un signifie vraiment que l'autre est toujours plus cher pour votre cerveau que de simplement travailler avec "m_value".

Pour plus de munitions et d'idées, je recommanderais de prendre une copie de l'oncle Bob Clean Code .

5
DXM

Je peux penser à une justification pour passer un champ membre comme paramètre dans une méthode (privée): cela rend explicite de quoi dépend votre méthode.

Comme vous le dites, tous les champs membres sont des paramètres implicites de votre méthode, car l'objet entier l'est. Cependant, l'objet complet est-il vraiment nécessaire pour calculer le résultat? Si SomeMethod est une méthode interne qui ne dépend que de _someField, n'est-il pas plus propre de rendre cette dépendance explicite? En fait, rendre cette dépendance explicite peut également suggérer que vous pouvez réellement refactoriser ce morceau de code hors de votre classe! (Notez que je suppose que nous ne parlons pas ici de getters ou setters, mais du code qui calcule réellement quelque chose)

Je ne ferais pas le même argument pour les méthodes publiques, car l'appelant ne sait ni ne se soucie de quelle partie de l'objet est pertinente pour calculer le résultat ...

31
Andres F.

Je vois une bonne raison de passer des variables membres comme arguments de fonction aux méthodes privées - pureté de la fonction. Les variables membres sont effectivement un état global du point de vue, de plus, un état global mutable si ledit membre change lors de l'exécution de la méthode. En remplaçant les références de variables membres par des paramètres de méthode, nous pouvons effectivement rendre une fonction pure. Les fonctions pures ne dépendent pas d'un état externe et n'ont aucun effet secondaire, renvoyant toujours les mêmes résultats pour le même ensemble de paramètres d'entrée, ce qui facilite les tests et la maintenance future.

Bien sûr, ce n'est ni facile ni pratique d'avoir toutes vos méthodes en tant que méthodes pures dans un langage OOP. Mais je crois que vous gagnez beaucoup en termes de clarté de code en ayant les méthodes qui gèrent la logique complexe pure et en utilisant variables immuables, tout en gardant la gestion d'état "globale" impure séparée dans des méthodes dédiées.

Passer des variables membres à une fonction publique du même objet lors de l'appel de la fonction en externe constituerait cependant à mon avis une odeur de code majeure.

30
scrwtp

Si la fonction est appelée plusieurs fois, en passant parfois cette variable membre et parfois en passant quelque chose d'autre, alors c'est OK. Par exemple, je ne considérerais pas cela du tout mauvais:

if ( CalculateCharges(newStartDate) > CalculateCharges(m_StartDate) )
{
     //handle increase in charges
}

newStartDate est une variable locale et m_StartDate est une variable membre.

Cependant, si la fonction n'est appelée que si la variable membre lui est transmise, c'est étrange. Les fonctions membres fonctionnent tout le temps sur les variables membres. Ils peuvent le faire (selon la langue dans laquelle vous travaillez) pour obtenir une copie de la variable membre - si c'est le cas et que vous ne pouvez pas le voir, le code pourrait être plus agréable s'il rendait l'ensemble du processus explicite.

8
Kate Gregory

Ce que personne n'a abordé, c'est que SomeMethod est protégé virtuel. Cela signifie qu'une classe dérivée peut l'utiliser ET réimplémenter sa fonctionnalité. Une classe dérivée n'aurait pas accès à la variable privée et ne pourrait donc pas fournir une implémentation personnalisée de SomeMethod qui dépend de la variable privée. Au lieu de prendre la dépendance sur la variable privée, la déclaration exige que l'appelant la transmette.

2
Michael Brown

La raison principale d'avoir une variable membre dans une classe est de vous permettre de la définir en un seul endroit, et d'avoir sa valeur disponible pour toutes les autres méthodes de la classe. En général, vous vous attendez donc à ce qu'il ne soit pas nécessaire de passer la variable membre dans une méthode de la classe.

Je peux cependant penser à quelques raisons pour lesquelles vous pourriez souhaiter passer la variable membre dans une autre méthode de la classe. La première consiste à garantir que la valeur de la variable membre doit être utilisée inchangée lorsqu'elle est utilisée avec la méthode appelée, même si cette méthode devra modifier la valeur réelle de la variable membre à un moment donné du processus. La deuxième raison est liée à la première en ce que vous souhaiterez peut-être garantir l'immuabilité d'une valeur dans le cadre d'une chaîne de méthodes - lors de la mise en œuvre d'une syntaxe fluide par exemple.

Avec tout cela à l'esprit, je n'irais pas jusqu'à dire que le code est "mauvais" en tant que tel si vous passez une variable membre à l'une des méthodes de sa classe. Je suggérerais cependant que ce n'est généralement pas idéal, car cela pourrait encourager beaucoup de duplication de code où le paramètre est affecté à une variable locale par exemple, et les paramètres supplémentaires ajoutent du "bruit" dans le code là où il n'est pas nécessaire. Si vous êtes un fan du livre Clean Code, vous savez qu'il mentionne que vous devez garder le nombre de paramètres de méthode au minimum, et seulement s'il n'y a pas d'autre moyen plus judicieux pour la méthode d'accéder au paramètre .

1
S.Robins

Les frameworks GUI ont généralement une sorte de classe 'View' qui représente les choses dessinées à l'écran, et cette classe fournit généralement une méthode comme invalidateRect(Rect r) pour marquer une partie de sa zone de dessin comme devant être redessinée. Les clients peuvent appeler cette méthode pour demander une mise à jour d'une partie de la vue. Mais une vue peut également appeler sa propre méthode, comme:

invalidateRect(m_frame);

pour provoquer un nouveau tracé de toute la zone. Par exemple, il peut le faire lors de son premier ajout à la hiérarchie des vues.

Il n'y a rien de mal à faire cela - le cadre de la vue est un rectangle valide et la vue elle-même sait qu'elle veut se redessiner. La classe View pourrait fournir une méthode distincte qui ne prend aucun paramètre et utilise le cadre de la vue à la place:

invalidateFrame();

Mais pourquoi ajouter une méthode spéciale juste pour cela quand vous pouvez utiliser la fonction invalidateRect() plus générale? Ou, si vous avez choisi de fournir invalidateFrame(), vous l'implémenteriez très probablement en termes de invalidateRect() plus général:

View::invalidateFrame(void)
{
    invalidateRect(m_frame)
}

Pourquoi passer la variable comme paramètre de méthode, si vous y avez déjà accès?

Vous devez transmettre des variables d'instance en tant que paramètres à votre propre méthode si la méthode ne fonctionne pas spécifiquement sur cette variable d'instance. Dans l'exemple ci-dessus, le cadre de la vue n'est qu'un autre rectangle en ce qui concerne la méthode invalidateRect().

1
Caleb

Cela a du sens si la méthode est une méthode utilitaire. Supposons par exemple que vous devez dériver un nom abrégé unique à partir de plusieurs chaînes de texte gratuites.

Vous ne voudriez pas coder une implémentation distincte pour chaque chaîne, mais plutôt passer la chaîne à une méthode commune est logique.

Cependant, si la méthode fonctionne toujours sur un seul membre, il semble un peu stupide de la passer en paramètre.

1
James Anderson

Certaines choses qui me viennent à l'esprit en y réfléchissant:

  1. En général, les signatures de méthode avec moins de paramètres sont plus faciles à comprendre; une grande raison pour laquelle des méthodes ont été inventées était d'éliminer les longues listes de paramètres en les mariant aux données sur lesquelles ils opèrent.
  2. Rendre la signature de la méthode dépendante de la variable membre rendra plus difficile le changement de ces variables à l'avenir, car vous devrez non seulement changer à l'intérieur de la méthode, mais partout où la méthode est également appelée. Et comme SomeMethod dans votre exemple est protégé, les sous-classes devront également changer.
  3. Les méthodes (publiques ou privées) qui ne dépendent pas de la classe interne n'ont pas besoin d'être sur cette classe. Ils pourraient être pris en compte dans les méthodes d'utilité et être tout aussi heureux. Ils n'ont pratiquement aucune entreprise faisant partie de cette classe. Si aucune autre méthode ne dépend de cette variable après que vous ayez déplacé la ou les méthodes, alors cette variable devrait aller aussi! Très probablement, la variable devrait être sur son propre objet avec cette méthode ou les méthodes qui opèrent sur elle devenant publique et étant composée par la classe parente.
  4. Passer des données à diverses fonctions comme votre classe est un programme procédural avec des variables globales qui va à l'encontre de la conception OO. Ce n'est pas l'intention des variables membres et des fonctions membres (voir ci-dessus), et il semble que votre classe ne soit pas très cohérente. Je pense que c'est une odeur de code qui suggère une meilleure façon de regrouper vos données et méthodes.
1
Colin Hunt

Vous manquez si le paramètre ("argument") est de référence ou en lecture seule.

class SomeClass
{
    protected SomeType _someField;
    public SomeType SomeField
    {
        get { return _someField; }

        set {
          if (doSomeValidation(value))
          {
            _someField = value;
          }
        }
    }

    protected virtual void ModifyMethod(/*...., */ ref SomeType someVar)
    { 
      // ...
    }    

    protected virtual void ReadMethod(/*...., */ SomeType someVar)
    { 
      // ...
    }

    private void SomeAnotherMethod()
    {
        //.............

        // not recommended, but, may be required in some cases
        ModifyMethod(ref this._someField);

        //.............

        // recommended, but, verbose
        SomeType SomeVar = this.someField;
        ModifyMethod(ref SomeVar);
        this.someField = SomeVar;

        //.............

        ReadMethod(this.someField);
        //.............
    }

};

De nombreux développeurs attribuent généralement directement les champs internes d'une variable dans les méthodes du constructeur. Il y a quelques exceptions.

N'oubliez pas que les "setters" peuvent avoir des méthodes supplémentaires, pas seulement des affectations, et parfois même des méthodes virtuelles ou appeler des méthodes virtuelles.

Remarque: je suggère de conserver les champs internes des propriétés comme "protégés".

0
umlcat