Y a-t-il une bonne raison de rendre les fonctions pures non publiques?

25

J'ai eu un petit débat en cours avec un collègue. Autrement dit, y a-t-il une bonne raison de cacher / encapsuler des fonctions qui sont pures?

Par "pur", je veux dire la définition de wikipedia :

  • Renvoie toujours les mêmes résultats à partir de la même entrée. (Pour les besoins de cette discussion, il Foo Create(){ return new Foo(); }est considéré comme impur s'il Foon'a pas de sémantique de valeur.)
  • N'utilise pas d'état mutable (sauf les variables locales) ou d'E / S.
  • Ne produit pas d'effets secondaires.
Telastyn
la source
26
Il pourrait y avoir un argument pour ne pas du tout rendre une telle fonction membre d'une classe.
Pieter B
6
@PieterB - en effet, bien que toutes les langues ne le prennent pas en charge, et certaines langues autorisent le module interne même pour des fonctions gratuites.
Telastyn
3
Quelle était votre opinion? Je ne pense pas que la pureté de la fonction soit liée à son appartenance à l'API publique.
Andres F.
@andresF. - le débat portait en fait sur la question de savoir si une règle de validation non liée devait être rendue publique. J'ai fait valoir que puisque c'était une fonction pure, il y avait peu de mal. Pour ce cas particulier, il a facilité la testabilité et était susceptible d'être réutilisé. Cette question porte davantage sur la manière dont cet argument pourrait / devrait s'appliquer.
Telastyn
2
@Telastyn S'il est réutilisable mais ne fait pas partie de la responsabilité de la classe actuelle, il devrait probablement se trouver dans une classe / un module / quel que soit votre langage. Faisant alors partie de la responsabilité du nouveau module / classe, il serait nécessairement rendu public. Puisque vous mentionnez le test, je noterai que vous n'avez pas nécessairement à vous moquer de la méthode lorsque vous faites cela. En tant que «détail d'implémentation», se moquer de lui pendant les tests donne peu d'avantages.
jpmc26

Réponses:

76

Une fonction pure pourrait encore être un détail d'implémentation. Bien que la fonction puisse ne causer aucun préjudice (du point de vue de ne pas rompre les invariants / contrats importants), en l'exposant, l'auteur et les utilisateurs de cette classe / module / package perdent. L'auteur perd parce qu'il ne peut plus le supprimer même si l'implémentation change et que la fonction ne lui est plus utile. Les utilisateurs perdent parce qu'ils doivent passer au crible et ignorer les fonctions supplémentaires qui ne sont pas pertinentes pour utiliser l'API afin de le comprendre.

Doval
la source
33
+1. Les membres publics de votre classe sont son API. Il ne s'agit pas de "Cela peut-il me blesser s'il est public?", Mais plutôt "Doit-il faire partie de l'API?"
Ordous
1
La seule chose que j'ajouterais, c'est que si vous commencez à voir des fonctions similaires apparaître dans d'autres emplacements, refactorisez-la dans une autre classe afin que plusieurs classes puissent l'utiliser. Ou si c'est un problème pour commencer, définissez-le dans une classe distincte pour commencer.
jpmc26
1
Je scellerais les classes publiques qui ne sont pas censées être prolongées aussi.
Den
+1 pour avoir souligné que masquer ou afficher une fonction (ou une classe) sont principalement des questions de protection et de maintenance d'une API, et non liées au type de code qu'il s'agit.
Marco
42

La question est à l'envers.

Vous ne cherchez pas de raison de rendre une fonction non publique. C'est un état d'esprit incorrect pour commencer (à mon avis). Le raisonnement devrait aller dans l'autre sens.

En d'autres termes - ne demandez pas "pourquoi devrais-je le rendre privé?". Demandez: "pourquoi devrais-je le rendre public?"

En cas de doute, ne l'exposez pas. C'est un peu comme le rasoir d'Ockham - ne multipliez pas les droits au-delà de la nécessité.

EDIT: Aborder les contre-arguments soulevés par @Telastyn dans les commentaires (pour éviter une discussion approfondie là-bas):

J'ai entendu cela au fil du temps et je l'ai même embrassé pendant un certain temps, mais d'après mon expérience, les choses ont tendance à être trop privées.

Oui, c'est parfois un problème si une classe est ouverte à l'héritage, mais vous ne pouvez pas remplacer certaines méthodes privées (dont vous souhaitez modifier le comportement).

Mais protectedcela suffirait - et ce n'est toujours pas public.

Cela conduit à beaucoup de duplication de code et de surcharge pour arriver à "des choses qui ne devraient pas être publiques" mais qui sont de toute façon accessibles indirectement.

Si cela devient problématique, rendez-le simplement public! Il y a la nécessité dont je parlais :)

Mon point est que vous ne devriez pas le faire au cas où (YAGNI et tout).

Notez qu'il est toujours plus facile de rendre publique une fonction privée que de la ramener à la confidentialité. Ce dernier est susceptible de casser le code existant.

Konrad Morawski
la source
J'ai entendu cela au fil du temps et je l'ai même embrassé pendant un certain temps, mais d'après mon expérience, les choses ont tendance à être trop privées. Cela conduit à beaucoup de duplication de code et de surcharge pour arriver à "des choses qui ne devraient pas être publiques" mais qui sont de toute façon accessibles indirectement.
Telastyn
3
@Telastyn IMHO, cela ressemble à l'application souffre de quelques erreurs de conception; si vous vous trouvez enclin à exposer une méthode parce que vous devez l'invoquer sur des chemins de code discrets, c'est probablement un signe que cette méthode doit être divisée en son propre module qui peut être injecté ou inclus le cas échéant, surtout s'il s'agit d'une fonction pure .
leo
@leo - désolé, je ne suis pas. Considérons une fonction comme entier inférieur à. C'est une belle fonction pure qui est exposée spécifiquement parce qu'elle peut ensuite être injectée et incluse (ou simplement utilisée) le cas échéant.
Telastyn
5
@Telastyn À mon avis, c'est un symptôme de la philosophie «tout est un objet / classe» combinée au fait que certains langages POO n'ont pas de types de données composites autres que les classes. Dès que quelqu'un doit passer deux valeurs en même temps, il finit par écrire une classe, puis chaque collègue et logiciel de vérification de style vous dira de rendre ces champs privés.
Doval
@Telastyn Right. Ce que je veux dire, c'est que si votre méthode 'integerLessThan' a commencé comme un détail d'implémentation encapsulé d'une méthode publique, mais vous trouvez que vous devez invoquer la méthode privée à d'autres endroits, c'est probablement un signe que la méthode privée appartient à un autre module / package / class afin qu'il puisse être importé indépendamment de la logique qui l'invoque. Le simple fait de publier la méthode telle quelle signifierait qu'elle se trouve arbitrairement dans le premier module où vous l'avez trouvée utile.
leo
6

Je ne pense pas que la décision de cacher / encapsuler une fonction devrait dépendre de sa pureté. Ce n'est pas parce qu'une fonction est pure que les externes doivent en être informés. Il est intéressant de noter que si la fonction est pure et destinée à être publique, elle n'a peut-être même pas besoin d'être un membre d'instance de l'interface, peut-être qu'elle est mieux adaptée en tant que statique. Mais encore une fois, tout cela dépend de l'intention du contrat et, dans ce cas, du regroupement logique des fonctionnalités et non de la pureté de la fonction.

implorer
la source
5

Les classes doivent adhérer au principe de responsabilité unique . Alors qu'une classe peut avoir besoin d'appeler d'autres fonctionnalités pour atteindre ses objectifs, elle ne doit exposer que les fonctions qui font partie de sa seule responsabilité.

Voici juste un exemple de cas où la visibilité pourrait causer un problème.

Considérez une classe qui frobnicate les widgets. Peut-être que dans le cadre de son code de frobnication, il a besoin d'une fonction utilitaire qui analyse une chaîne: il doit peut-être transformer le nom du widget d'une manière que les fonctions de chaîne standard ne prennent pas en charge.

Comme il s'agit d'une fonction pure (une chaîne entre, la transforme en quelque sorte, retourne une nouvelle chaîne), elle peut être publique ou privée sans conséquence. Ou est-ce possible?

Si vous le rendez public, votre classe a maintenant deux responsabilités: frobnicating widgets et transforming strings. Cela viole SRP et peut provoquer des problèmes si d'autres classes dépendent de la fonction. Comme vous pensez que c'est quelque chose qui n'est utilisé qu'en interne à la classe, vous pouvez peut-être changer son interface ou sa visibilité. Désormais, les classes des autres parties du système sont interrompues.

En gardant la fonction privée, personne n'a jamais la possibilité de s'appuyer sur du code qui ne fait pas partie de la responsabilité unique de la classe.


la source
2
Je dirais qu'il ne devrait contenir que des fonctions qui relèvent de sa seule responsabilité, et pas seulement les exposer.
Telastyn
1
Je pense qu'il y a une zone grise. Avez-vous vraiment besoin d'une StringTransformerclasse distincte pour encapsuler deux ou trois lignes de code qui ne sont utilisées qu'à un seul endroit? Je suis d'accord qu'une fois le code utilisé à plusieurs endroits, il est préférable de le séparer en une nouvelle classe avec une seule responsabilité, mais il y a un compromis.
Certainement. Les lignes directrices sont des lignes directrices, pas des règles.
Telastyn
@snowman en non java vous faites juste une bibliothèque de fonctions.
Pieter B
@PieterB il ne s'agit pas de Java contre quoi que ce soit d'autre. Pour le rendre vraiment OO, vous auriez besoin d'une usine, de classes abstraites, etc.