Pourquoi Java a-t-il rendu l'accès aux packages par défaut?

26

Je pose cette question parce que je pense qu'ils l'ont fait pour une très bonne raison et que la plupart des gens ne l'utilisent pas correctement, d'après mon expérience dans l'industrie jusqu'à présent de toute façon. Mais si ma théorie est vraie, je ne sais pas pourquoi ils ont inclus le modificateur d'accès privé ...?

Je crois que si l'accès par défaut est correctement utilisé, il offre une testabilité améliorée tout en maintenant l'encapsulation. Et il rend également le modificateur d'accès privé redondant.

Le modificateur d'accès par défaut peut être utilisé pour fournir le même effet en utilisant un package unique pour les méthodes qui doivent être cachées du reste du monde, et il le fait sans compromettre la testabilité, car les packages dans un dossier de test, avec le même sont capable d'accéder à toutes les méthodes par défaut déclarées dans un dossier source.

Je pense que c'est la raison pour laquelle Java utilise l'accès aux packages comme «par défaut». Mais je ne sais pas pourquoi ils ont également inclus un accès privé, je suis sûr qu'il existe un cas d'utilisation valide ...

newlogic
la source
Pertinent en ce qui concerne les méthodes privées de test unitaire précédemment discutées; comment-vous-testez-les-méthodes-privées . Répondre; vous ne devriez pas
Richard Tingle

Réponses:

25

Je suppose qu'ils avaient une bonne idée de ce qu'un programmeur moyen ferait. Et par programmeur moyen, je veux dire celui qui n'est pas vraiment bon en programmation, mais qui réussit quand même parce qu'il n'y en a pas assez et qu'ils sont chers.

S'ils rendaient l'accès "public" par défaut, la plupart des programmeurs ne prendraient jamais la peine d'utiliser autre chose. Le résultat serait beaucoup de code spaghetti partout. (Parce que si vous êtes techniquement autorisé à appeler n'importe quoi de n'importe où, pourquoi vous embêter à encapsuler une logique dans une méthode?)

Rendre "privé" l'accès par défaut ne serait que légèrement meilleur. La plupart des programmeurs débutants inventeraient simplement (et partageraient sur leurs blogs) une règle empirique selon laquelle "vous devez écrire" public "partout", puis ils se plaindraient pourquoi Java est si mauvais qu'il les oblige à écrire "public" partout. Et ils produiraient également beaucoup de code spaghetti.

L'accès aux packages est quelque chose qui permet aux programmeurs d'utiliser leurs techniques de programmation bâclées lors de l'écriture de code dans un package, mais ils doivent ensuite le reconsidérer lors de la création de plusieurs packages. Il s'agit d'un compromis entre les bonnes pratiques commerciales et la réalité laide. Comme: écrivez du code spaghetti, si vous insistez, mais veuillez laisser le désordre laid dans le paquet; au moins créer des interfaces plus agréables parmi les packages.

Il y a probablement d'autres raisons aussi, mais je ne sous-estimerais pas celle-ci.

Viliam Búr
la source
14

L'accès par défaut ne rend pas le modificateur d'accès privé redondant.

La position des concepteurs de langage à ce sujet se reflète dans le tutoriel officiel - Contrôle de l'accès aux membres d'une classe et c'est assez clair (pour votre commodité, la déclaration pertinente dans la citation est mise en gras ):

Conseils sur le choix d'un niveau d'accès:

Si d'autres programmeurs utilisent votre classe, vous voulez vous assurer que les erreurs de mauvaise utilisation ne peuvent pas se produire. Les niveaux d'accès peuvent vous y aider.

  • Utilisez le niveau d'accès le plus restrictif qui a du sens pour un membre particulier. Utilisez privé sauf si vous avez une bonne raison de ne pas le faire.
  • Évitez les champs publics à l'exception des constantes. (De nombreux exemples du didacticiel utilisent des champs publics. Cela peut aider à illustrer certains points de manière concise, mais n'est pas recommandé pour le code de production.) Les champs publics ont tendance à vous lier à une implémentation particulière et à limiter votre flexibilité dans la modification de votre code.

Votre appel à la testabilité comme justification de la suppression complète du modificateur privé est faux, comme en témoignent par exemple les réponses dans Nouveau dans TDD. Dois-je éviter les méthodes privées maintenant?

Bien sûr, vous pouvez avoir des méthodes privées et bien sûr, vous pouvez les tester.

Soit il y a un moyen de faire fonctionner la méthode privée, auquel cas vous pouvez la tester de cette façon, soit il n'y a aucun moyen de faire exécuter la méthode privée, dans ce cas: pourquoi diable essayez-vous de la tester, juste supprimer la fichue chose ...


La position des concepteurs de langage sur le but et l'utilisation de l'accès au niveau du package est expliquée dans un autre didacticiel officiel, Création et utilisation de packages et il n'a rien en commun avec l'idée de supprimer des modificateurs privés (pour votre commodité, la déclaration pertinente dans la citation est mise en gras ) :

Vous devez regrouper ces classes et l'interface dans un package pour plusieurs raisons, notamment les suivantes:

  • Vous et d'autres programmeurs pouvez facilement déterminer que ces types sont liés ...
  • Vous pouvez autoriser les types dans le package à avoir un accès illimité les uns aux autres tout en limitant l'accès aux types en dehors du package ...

<rant "Je pense que j'ai entendu assez de gémissements. Je suppose qu'il est temps de dire haut et fort ...">

Les méthodes privées sont avantageuses pour les tests unitaires.

La note ci-dessous suppose que vous connaissez la couverture du code . Sinon, prenez le temps d'apprendre, car il est très utile pour ceux qui s'intéressent aux tests unitaires et aux tests.

Très bien, j'ai donc cette méthode privée et des tests unitaires, et une analyse de couverture me disant qu'il y a un écart, ma méthode privée n'est pas couverte par les tests. À présent...

Qu'est-ce que je gagne à le garder privé

Puisque la méthode est privée, la seule façon de procéder est d'étudier le code pour savoir comment il est utilisé via une API non privée. En règle générale, une telle étude révèle que la raison de l'écart est que le scénario d'utilisation particulier manque dans les tests.

    void nonPrivateMethod(boolean condition) {
        if (condition) {
            privateMethod();
        }
        // other code...
    }

    // unit tests don't invoke nonPrivateMethod(true)
    //   => privateMethod isn't covered.

Dans un souci d'exhaustivité, d'autres raisons (moins fréquentes) pour de telles lacunes de couverture pourraient être des bogues dans la spécification / conception. Je ne vais pas m'y plonger profondément ici, pour garder les choses simples; il suffit de dire que si vous affaiblissez la limitation d'accès "juste pour rendre la méthode testable", vous manquerez une chance d'apprendre que ces bogues existent.

Très bien, pour corriger l'écart, j'ajoute un test unitaire pour le scénario manquant, répète l'analyse de couverture et vérifie que l'écart est disparu. Qu'est-ce que j'ai maintenant? J'ai comme nouveau test unitaire pour une utilisation spécifique de l'API non privée.

  1. Un nouveau test garantit que le comportement attendu pour cette utilisation ne changera pas sans préavis car s'il change, le test échouera.

  2. Un lecteur externe peut examiner ce test et apprendre comment il est censé être utilisé et se comporter (ici, le lecteur externe inclut mon futur moi-même, car j'ai tendance à oublier le code un mois ou deux après en avoir fini).

  3. Un nouveau test est tolérant au refactoring (dois-je refactoriser des méthodes privées? Vous pariez!) Quoi que je fasse privateMethod, je veux toujours tester nonPrivateMethod(true). Peu importe ce que je fais privateMethod, il ne sera pas nécessaire de modifier le test car la méthode n'est pas directement invoquée.

Pas mal? Tu paries.

Qu'est-ce que je perds de l'affaiblissement de la limitation d'accès

Imaginez maintenant qu'au lieu de ci-dessus, j'affaiblisse simplement la limitation d'accès. Je saute l'étude du code qui utilise la méthode et je continue directement avec le test qui invoque mon exPrivateMethod. Génial? Ne pas!

  1. Dois-je passer un test pour une utilisation spécifique des API non privées mentionnées ci-dessus? Non: il n'y avait pas de test pour nonPrivateMethod(true)avant, et il n'y en a plus actuellement.

  2. Les lecteurs externes ont-ils une chance de mieux comprendre l'utilisation de la classe? Non. "- Hé, quel est le but de la méthode testée ici? - Oubliez ça, c'est strictement pour un usage interne. - Oups."

  3. Est-il tolérant au refactoring? Pas question: quoi que je change exPrivateMethod, cela cassera probablement le test. Renommer, fusionner dans une autre méthode, changer les arguments et tester arrêtera simplement la compilation. Mal de tête? Tu paries!

En résumé , m'en tenir à une méthode privée m'apporte une amélioration utile et fiable dans les tests unitaires. En revanche, l'affaiblissement des limitations d'accès "pour la testabilité" ne me donne qu'un morceau de code de test obscur et difficile à comprendre, qui risque en outre d'être définitivement rompu par une refactorisation mineure; franchement ce que j'obtiens ressemble étrangement à une dette technique .

</rant>

moucheron
la source
7
Je n'ai jamais trouvé cet argument pour tester des méthodes privées convaincant. Vous refactorisez le code en méthodes tout le temps pour des raisons qui n'ont rien à voir avec l'API publique d'une classe, et c'est une très bonne chose de pouvoir tester ces méthodes indépendamment, sans impliquer d'autre code. C'est la raison pour laquelle vous avez refactorisé, non? Pour obtenir un morceau de fonctionnalité indépendante. Cette fonctionnalité devrait également pouvoir être testée indépendamment.
Robert Harvey
La réponse @RobertHarvey s'est développée avec une diatribe traitant de cela. "Les méthodes privées sont bénéfiques pour les tests unitaires ..."
gnat
Je vois ce que vous dites sur les méthodes privées et la couverture du code, mais je ne préconisais pas vraiment de rendre ces méthodes publiques pour que vous puissiez les tester. Je préconisais d'avoir un moyen de les tester indépendamment, même s'ils sont privés. Vous pouvez toujours avoir vos tests publics qui touchent la méthode privée, si vous le souhaitez. Et je peux avoir mes tests privés.
Robert Harvey
@RobertHarvey je vois. Je suppose que cela se résume au style de codage. À la quantité de méthodes privées que je produis généralement, et au rythme où je les refaçonne, les tester est tout simplement un luxe que je ne peux pas me permettre. L'API non privée est une autre affaire, j'ai tendance à être assez réticent et lent à le modifier
gnat
Vous savez peut-être mieux écrire des méthodes qui fonctionnent la première fois que moi. Pour moi, je dois tester la méthode pour m'assurer qu'elle fonctionne d'abord avant de pouvoir continuer, et devoir la tester indirectement en mettant à feu une série d'autres méthodes serait maladroit et maladroit.
Robert Harvey
9

La raison la plus probable est: cela a à voir avec l'histoire. L'ancêtre de Java, Oak, n'avait que trois niveaux d'accès: privé, protégé, public.

Sauf que private dans Oak était l'équivalent du package private dans Java. Vous pouvez lire la section 4.10 de la spécification de langue d' Oak (c'est moi qui souligne):

Par défaut, toutes les variables et méthodes d'une classe (y compris les constructeurs) sont privées. Les variables et méthodes privées ne sont accessibles que par les méthodes déclarées dans la classe, et non par ses sous-classes ou toute autre classe (à l' exception des classes du même package ).

Donc à votre point, l'accès privé, comme connu en Java, n'était pas là à l'origine. Mais lorsque vous avez plus de quelques classes dans un package, ne pas avoir privé comme nous le savons maintenant conduirait à un cauchemar de collision de noms (par exemple, java.until.concurrent a près de 60 classes), c'est probablement pourquoi ils l'a présenté.

Cependant, la sémantique d'accès au package par défaut (initialement appelée privée) n'a pas été modifiée entre Oak et Java.

assylias
la source
5

Je crois que si l'accès par défaut est correctement utilisé, il offre une testabilité améliorée tout en maintenant l'encapsulation. Et il rend également le modificateur d'accès privé redondant.

Il y a des situations où l'on voudrait deux classes distinctes qui sont plus étroitement liées que de tout exposer au public. Cela a des idées similaires d '«amis» en C ++ où une autre classe qui est déclarée «amie» d'une autre peut accéder à ses membres privés.

Si vous regardez dans les entrailles de classes telles que BigInteger, vous trouverez un certain nombre de protections par défaut des packages sur les champs et les méthodes (tout ce qui est un triangle bleu dans la liste). Cela permet aux autres classes du package java.math d'avoir un accès plus optimal à leurs entrailles pour des opérations spécifiques (vous pouvez trouver ces méthodes appelées dans BigDecimal - BigDecimal est soutenu par un BigInteger et évite de réimplémenter BigInteger à nouveau . t un problème de protection car java.math est un package scellé et aucune autre classe ne peut y être ajoutée.

D'un autre côté, il y a beaucoup de choses qui sont en effet privées, comme elles devraient l'être. La plupart des colis ne sont pas scellés. Sans privé, votre collègue pourrait mettre une autre classe dans ce package et accéder au champ (plus) privé et rompre l'encapsulation.

Il convient de noter que les tests unitaires n'étaient pas une chose à laquelle on pensait à l'époque de la construction des étendues de protection. JUnit (le framework de test XUnit pour Java) n'a été conçu qu'en 1997 (un récit de cette histoire peut être lu sur http://www.martinfowler.com/bliki/Xunit.html ). Les versions Alpha et Beta du JDK étaient en 1995 et JDK 1.0 en 1996, bien que les niveaux de protection n'aient pas vraiment été cloués avant JDK 1.0.2 (avant cela, vous pouviez avoir un private protectedniveau de protection).

Certains diront que la valeur par défaut ne devrait pas être au niveau du package, mais privée. D'autres soutiennent qu'il ne devrait pas y avoir de protection par défaut mais tout devrait être explicitement déclaré - certains adeptes de cela écriront du code tel que:

public class Foo {
    /* package */ int bar = 42;
    ...
}

Notez le commentaire ici.

La vraie raison pour laquelle la protection au niveau du package est la valeur par défaut est probablement perdue dans les notes de conception de Java (j'ai creusé et je ne trouve pas pourquoi les articles, beaucoup de gens expliquent la différence, mais aucun ne dit "c'est la raison ").

Je vais donc deviner. Et c'est tout ce que c'est - une supposition.

Tout d'abord, les gens essayaient toujours de comprendre la conception du langage. Depuis lors, les langues ont appris des erreurs et des succès de Java. Cela pourrait être répertorié comme une erreur de ne pas avoir quelque chose qui doit être défini pour tous les champs.

  • Les concepteurs ont vu un besoin occasionnel d'une relation «amie» de C ++.
  • Les concepteurs voulaient des déclarations explicites pour public, et privatecomme celles-ci ont les plus grands impacts sur l'accès.

Ainsi, privé n'est pas par défaut et bien, tout le reste s'est mis en place. La portée par défaut a été laissée par défaut. C'est peut -être la première portée qui a été créée et dans l'intérêt d'une compatibilité ascendante rigoureuse avec le code plus ancien, elle a été laissée de cette façon.

Comme je l'ai dit, tout cela n'est qu'une supposition .


la source
Ah si seulement la fonction friend pouvait être appliquée aux membres sur une base individuelle, vous pourriez donc dire que void someFunction () ne peut être vu que par la classe X ... cela resserrerait vraiment l'encapsulation!
newlogic
Oh, attendez, vous pouvez le faire en C ++, nous en avons besoin en Java!
newlogic
2
@ user1037729 cela permet un couplage plus étroit entre les deux classes - la classe avec la méthode doit maintenant connaître les autres classes qui sont ses amis. Cela va généralement à l'encontre de la philosophie de conception fournie par Java. L'ensemble des problèmes qui peuvent être résolus avec des amis mais pas la protection au niveau du package n'est pas si important.
2

L'encapsulation est une façon de dire "vous n'avez pas à y penser".

                 Access Levels
Modifier    Class   Package  Subclass   World
public        Y        Y        Y        Y
protected     Y        Y        Y        N
no modifier   Y        Y        N        N
private       Y        N        N        N

Mettre ces termes en termes non techniques.

  1. Public: tout le monde doit y penser.
  2. Protégé: par défaut et les sous-classes doivent le savoir.
  3. Par défaut, si vous travaillez sur le paquet, vous le saurez (il est juste là devant vos yeux) pourrait tout aussi bien vous laisser en profiter.
  4. Privé, vous n'avez besoin de le savoir que si vous travaillez sur ce cours.
jmoreno
la source
Je ne pense pas qu'il existe une classe privée ou une classe protégée ..
Koray Tugay
@KorayTugay: consultez docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html . La classe intérieure est privée. Lisez le dernier paragraphe.
jmoreno
0

Je ne pense pas qu'ils se soient concentrés sur les tests, simplement parce que les tests n'étaient pas très courants à l'époque.

Ce que je pense qu'ils voulaient réaliser, c'était l'encapsulation au niveau du package. Une classe peut, comme vous le savez, avoir des méthodes et des champs internes et n'en exposer qu'une partie par le biais de membres publics. De la même manière, un package peut avoir des classes, des méthodes et des champs internes et n'en exposer qu'une partie. Si vous pensez de cette façon, un package a une implémentation interne dans un ensemble de classes, méthodes et champs, ainsi qu'une interface publique dans un autre ensemble (éventuellement se chevauchant partiellement) de classes, méthodes et champs.

Les codeurs les plus expérimentés que je connais pensent de cette façon. Ils prennent l'encapsulation et l'appliquent à un niveau d'abstraction supérieur à la classe: un package ou un composant si vous le souhaitez. Il me semble raisonnable que les concepteurs de Java soient déjà aussi matures dans leurs conceptions, compte tenu de la résistance de Java.

Alexander Torstling
la source
Vous avez raison de dire que les tests automatisés n'étaient pas très courants là-bas. Mais c'est un design immature.
Tom Hawtin - tackline