web-dev-qa-db-fra.com

Pourquoi Clean Code suggère-t-il d'éviter les variables protégées?

Clean Code suggère d'éviter les variables protégées dans la section "Distance verticale" du chapitre "Formatage":

Les concepts étroitement liés doivent être maintenus verticalement proches les uns des autres. De toute évidence, cette règle ne fonctionne pas pour les concepts qui appartiennent à des fichiers distincts. Mais alors, les concepts étroitement liés ne doivent pas être séparés en différents fichiers, sauf si vous avez une très bonne raison. En effet, c'est l'une des raisons pour lesquelles les variables protégées doivent être évitées .

Quel est le raisonnement?

174
Matsemann

Les variables protégées doivent être évitées car:

  1. Ils ont tendance à entraîner YAGNI problèmes. Sauf si vous avez une classe descendante qui fait des trucs avec le membre protégé, rendez-la privée.
  2. Ils ont tendance à entraîner LSP problèmes. Les variables protégées ont généralement une invariance intrinsèque qui leur est associée (sinon elles seraient publiques). Les héritiers doivent alors maintenir ces propriétés, que les gens peuvent gâcher ou violer volontairement.
  3. Ils ont tendance à violer OCP . Si la classe de base fait trop d'hypothèses sur le membre protégé, ou que l'héritier est trop flexible avec le comportement de la classe, cela peut entraîner la modification du comportement de la classe de base par cette extension.
  4. Ils ont tendance à conduire à l'héritage d'extension plutôt qu'à la composition. Cela tend à conduire à un couplage plus serré, à plus de violations de SRP , à des tests plus difficiles et à une foule d'autres choses qui relèvent de la `` composition privilégiée par rapport à l'héritage ''. discussion.

Mais comme vous le voyez, tout cela est "tendance". Parfois, un membre protégé est la solution la plus élégante. Et les fonctions protégées ont tendance à avoir moins de ces problèmes. Mais il y a un certain nombre de choses qui les obligent à être traités avec soin. Avec tout ce qui nécessite ce genre de soin, les gens feront des erreurs et dans le monde de la programmation, cela signifie des bugs et des problèmes de conception.

281
Telastyn

C'est vraiment la même raison pour laquelle vous évitez les globales, juste à plus petite échelle. C'est parce qu'il est difficile de trouver partout où une variable est lue, ou pire, écrite, et difficile de rendre l'utilisation cohérente. Si l'utilisation est limitée, comme être écrit en n endroit évident dans la classe dérivée et lu en n endroit évident dans la classe de base, alors les variables protégées peuvent rendre le code plus clair et concis. Si vous êtes tenté de lire et d'écrire une variable à volonté dans plusieurs fichiers, il est préférable de l'encapsuler.

54
Karl Bielefeldt

Je n'ai pas lu le livre, mais je peux essayer de comprendre ce que l'oncle Bob voulait dire.

Si vous mettez protected sur quelque chose, cela signifie qu'une classe peut en hériter. Mais les variables membres sont censées appartenir à la classe dans laquelle elles sont contenues; cela fait partie de l'encapsulation de base. Mettre protected sur une variable membre interrompt l'encapsulation car maintenant une classe dérivée a accès aux détails d'implémentation de la classe de base. C'est le même problème qui se produit lorsque vous créez une variable public sur une classe ordinaire.

Pour corriger le problème, vous pouvez encapsuler la variable dans une propriété protégée, comme suit:

protected string Name
{
    get { return name; }
    private set { name = value; }
}

Cela permet à name d'être défini en toute sécurité à partir d'une classe dérivée à l'aide d'un argument constructeur, sans exposer les détails d'implémentation de la classe de base.

27
Robert Harvey

L'argument de l'oncle Bob est principalement basé sur la distance: si vous avez un concept qui est important pour une classe, regroupez le concept avec la classe dans ce fichier. Non séparé sur deux fichiers sur le disque.

Les variables membres protégées sont dispersées à deux endroits et ressemblent un peu à la magie. Vous référencez cette variable, mais elle n'est pas définie ici ... où est elle est définie? Et ainsi commence la chasse. Mieux vaut éviter protected tout à fait, son argument.

Maintenant, je ne crois pas que la règle doit être respectée à la lettre tout le temps (comme: tu n'utiliseras pas protected). Regardez le esprit de ce qu'il veut dire ... regrouper les choses liées dans un seul fichier - utilisez des techniques et des fonctionnalités de programmation pour le faire. Je vous recommande de ne pas trop analyser et de vous laisser entraîner dans les détails à ce sujet.

18
J. Polfer

De très bonnes réponses déjà ici. Mais une chose à ajouter:

Dans la plupart des langages modernes OO c'est une bonne habitude (dans Java AFAIK son nécessaire) de mettre chaque classe dans son propre fichier (veuillez ne sur C++ où vous avez souvent deux fichiers).

Il est donc pratiquement impossible de garder les fonctions d'une classe dérivée accédant à une variable protégée d'une classe de base "verticalement proche" dans le code source de la définition de la variable. Mais idéalement, ils devraient y être conservés, car les variables protégées sont destinées à un usage interne, ce qui fait que les fonctions qui y accèdent sont souvent "un concept étroitement lié".

9
Doc Brown

L'idée de base est qu'un "champ" (variable au niveau de l'instance) qui est déclaré protégé est probablement plus visible qu'il ne doit l'être et moins "protégé" que vous ne le souhaiteriez. Il n'y a pas de modificateur d'accès en C/C++/Java/C # qui est l'équivalent de "accessible uniquement par les classes enfants au sein du même assembly", vous permettant ainsi de définir vos propres enfants qui peuvent accéder au champ dans votre assembly, mais ne pas permettre aux enfants créés dans d'autres assemblées le même accès; C # a des modificateurs internes et protégés, mais leur combinaison rend l'accès "interne ou protégé", pas "interne et protégé". Ainsi, un champ protégé est accessible à tout enfant, que vous ayez écrit cet enfant ou que quelqu'un d'autre l'ait fait. Protégé est ainsi une porte ouverte à un pirate.

De plus, les champs, par leur définition, n'ont pratiquement aucune validation inhérente à leur modification. en C #, vous pouvez en créer un en lecture seule, ce qui rend les types de valeur effectivement constants et les types de référence ne pouvant pas être réinitialisés (mais toujours très modifiables), mais c'est tout. En tant que tels, même protégés, vos enfants (auxquels vous ne pouvez pas faire confiance) ont accès à ce champ et peuvent le définir sur quelque chose de non valide, ce qui rend l'état de l'objet incohérent (quelque chose à éviter).

La façon acceptée de travailler avec les champs est de les rendre privés et d'y accéder avec une propriété et/ou une méthode getter et setter. Si tous les consommateurs de la classe ont besoin de la valeur, rendez le getter (au moins) public. Si seuls les enfants en ont besoin, protégez le getter.

Une autre approche qui répond à la question est de vous demander; Pourquoi le code d'une méthode enfant doit-il pouvoir modifier directement mes données d'état? Qu'est-ce que cela dit sur ce code? C'est l'argument de la "distance verticale" sur son visage. S'il y a un code dans un enfant qui doit directement modifier l'état parent, alors peut-être que ce code devrait appartenir au parent en premier lieu?

4
KeithS

C'est une discussion intéressante.

Pour être franc, de bons outils peuvent atténuer bon nombre de ces problèmes "verticaux". En fait, à mon avis, la notion de "fichier" est en fait quelque chose qui entrave le développement logiciel - quelque chose sur lequel travaille le projet Light Table (http://www.chris-granger.com/2012/04/12/light- table --- a-new-ide-concept /).

Supprimez les fichiers de l'image et la portée protégée devient beaucoup plus attrayante. Le concept protégé devient plus clair dans des langages comme Smalltalk - où tout héritage étend simplement le concept original d'une classe. Il devrait faire tout et avoir tout ce que la classe parent a fait.

À mon avis, les hiérarchies de classes devraient être aussi peu profondes que possible, la plupart des extensions provenant de la composition. Dans de tels modèles, je ne vois aucune raison pour que les variables privées pas soient protégées à la place. Si la classe étendue doit représenter une extension incluant tous les comportements de la classe parente (ce que je pense qu'elle devrait, et donc que les méthodes privées sont intrinsèquement mauvaises), pourquoi la classe étendue ne devrait-elle pas représenter également le stockage de ces données?

En d'autres termes - dans tous les cas où je pense qu'une variable privée conviendrait mieux qu'une variable protégée, l'hérédité n'est pas la solution au problème en premier lieu.

4
spronkey

Comme il est dit ici, ce n'est qu'une des raisons, c'est-à-dire que le code lié logiquement doit être placé dans des entités liées physiques (fichiers, packages et autres). À une échelle plus petite, c'est la même chose que la raison pour laquelle vous ne placez pas de classe qui effectue des requêtes DB à l'intérieur du package avec des classes qui affichent l'interface utilisateur.

Cependant, la principale raison pour laquelle les variables protégées ne sont pas recommandées est qu'elles ont tendance à rompre l'encapsulation. Les variables et les méthodes devraient avoir une visibilité aussi limitée que possible; pour plus de référence, voir Joshua Bloch Effective Java, Item 13 - "Minimize the accessibility of classes and members".

Cependant, vous ne devez pas prendre tout cela ad litteram, uniquement comme ligne de guilde; si les variables protégées sont très mauvaises, elles n'auraient pas été placées dans le langage en premier lieu. Un emplacement raisonnable pour les champs protégés à mon humble avis est à l'intérieur de la classe de base à partir d'un cadre de test (les classes de test d'intégration l'étendent).

0
m3th0dman

Je trouve que l'utilisation de variables protégées pour les tests JUnit peut être utile. Si vous les rendez privés, vous ne pouvez pas y accéder pendant le test sans réflexion. Cela peut être utile si vous testez un processus complexe à travers de nombreux changements d'état.

Vous pouvez les rendre privés, avec des méthodes getter protégées, mais si les variables ne doivent être utilisées qu'en externe lors d'un test JUnit, je ne suis pas sûr que ce soit une meilleure solution.

0
Nick