Por que o Java tornou o acesso ao pacote padrão?

26

Estou fazendo essa pergunta porque acredito que eles fizeram isso por um motivo muito bom e que a maioria das pessoas não o usa corretamente, bem, de qualquer maneira, pela minha experiência na indústria até agora. Mas se minha teoria é verdadeira, não sei por que eles incluíram o modificador de acesso privado ...?

Acredito que, se o acesso padrão for usado corretamente, ele fornece testabilidade aprimorada enquanto mantém o encapsulamento. E também torna o modificador de acesso privado redundante.

O modificador de acesso padrão pode ser usado para fornecer o mesmo efeito, usando um pacote exclusivo para métodos que precisam ser ocultos do resto do mundo, e isso sem comprometer a testabilidade, pois os pacotes em uma pasta de teste com o mesmo capaz de acessar todos os métodos padrão declarados em uma pasta de origem.

Eu acredito que é por isso que Java usa o acesso ao pacote como 'padrão'. Mas não sei por que eles também incluíram acesso privado, tenho certeza de que há um caso de uso válido ...

newlogic
fonte
Relevante em relação aos métodos particulares de teste de unidade discutidos anteriormente; como-você-unidade-teste-privado-métodos . Responda; você não deveria
Richard Tingle

Respostas:

25

Eu acho que eles tinham uma boa idéia do que um programador comum faria. E por programador médio, quero dizer um que não é realmente bom em programação, mas consegue o emprego de qualquer maneira porque não há bons o suficiente e são caros.

Se eles tornassem o acesso "público" o padrão, a maioria dos programadores nunca se incomodaria em usar outra coisa. O resultado seria um monte de código espaguete em todos os lugares. (Como se você está tecnicamente autorizado a chamar qualquer coisa de qualquer lugar, por que se preocupar em encapsular alguma lógica dentro de um método?)

Tornar "privado" o acesso padrão seria apenas um pouco melhor. A maioria dos programadores iniciantes simplesmente inventava (e compartilhava em seus blogs) uma regra básica de que "você precisa escrever 'público' em todos os lugares" ", e então eles reclamavam por que o Java é tão ruim que os obriga a escrever" público "em todos os lugares. E eles também produziriam muito código de espaguete.

O acesso ao pacote é algo que permite que os programadores usem suas técnicas de programação desleixada enquanto escrevem código em um pacote, mas precisam reconsiderá-lo ao criar mais pacotes. É um compromisso entre boas práticas de negócios e a realidade feia. Como: escreva um código espaguete, se você insistir, mas por favor deixe a bagunça feia dentro do pacote; pelo menos crie algumas interfaces mais agradáveis ​​entre os pacotes.

Provavelmente, há outras razões também, mas eu não subestimaria essa.

Viliam Búr
fonte
14

O acesso padrão não torna o modificador de acesso privado redundante.

A posição dos designers de idiomas é refletida no tutorial oficial - Controlando o acesso aos membros de uma classe e é bastante claro (para sua conveniência, declaração relevante na citação em negrito ):

Dicas para escolher um nível de acesso:

Se outros programadores usarem sua classe, você deseja garantir que erros de uso indevido não ocorram. Os níveis de acesso podem ajudá-lo a fazer isso.

  • Use o nível de acesso mais restritivo que faça sentido para um membro específico. Use privado, a menos que tenha um bom motivo para não fazê-lo.
  • Evite campos públicos, exceto constantes. (Muitos dos exemplos do tutorial usam campos públicos. Isso pode ajudar a ilustrar alguns pontos de maneira concisa, mas não é recomendado para o código de produção.) Os campos públicos tendem a vincular você a uma implementação específica e limitar sua flexibilidade na alteração do código.

Seu apelo à testabilidade como justificativa para descartar completamente o modificador privado está errado, como evidenciado, por exemplo, pelas respostas em Novo no TDD. Devo evitar métodos privados agora?

Claro que você pode ter métodos particulares e, claro, pode testá-los.

Existe alguma maneira de executar o método privado; nesse caso, você pode testá-lo dessa maneira ou não como executar o privado, nesse caso: por que diabos você está tentando testá-lo, apenas excluir a coisa maldita ...


A posição dos designers de idiomas sobre a finalidade e o uso do acesso no nível do pacote é explicada em outro tutorial oficial, Criando e usando pacotes e não tem nada em comum com a ideia de eliminar modificadores privados (para sua conveniência, declaração relevante na citação em negrito ) :

Você deve agrupar essas classes e a interface em um pacote por vários motivos, incluindo o seguinte:

  • Você e outros programadores podem determinar facilmente que esses tipos estão relacionados ...
  • Você pode permitir que tipos dentro do pacote tenham acesso irrestrito um ao outro e ainda assim restringir o acesso a tipos fora do pacote ...

<BR> "Acho que ouvi o suficiente choramingando. Acho que está na hora de dizer alto e claro ...">

Métodos particulares são benéficos para o teste de unidade.

A nota abaixo pressupõe que você esteja familiarizado com a cobertura do código . Caso contrário, reserve um tempo para aprender, pois é bastante útil para os interessados ​​em testes de unidade e em todos os testes.

Tudo bem, então eu tenho esse método privado, testes de unidade e análise de cobertura me dizendo que há uma lacuna, meu método privado não é coberto por testes. Agora...

O que eu ganho ao mantê-lo privado

Como o método é privado, a única maneira de prosseguir é estudar o código para aprender como ele é usado por meio de API não privada. Normalmente, esse estudo revela que o motivo da lacuna é que um cenário de uso específico está ausente nos testes.

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

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

Por uma questão de integridade, outras razões (menos freqüentes) para essas lacunas de cobertura podem ser erros na especificação / design. Não vou me aprofundar nisto aqui, para manter as coisas simples; basta dizer que, se você enfraquecer a limitação de acesso "apenas para tornar o método testável", perderá a chance de descobrir que esses erros existem.

Tudo bem, para corrigir a lacuna, adiciono um teste de unidade para o cenário ausente, repito a análise de cobertura e verifico se a lacuna desapareceu. O que eu tenho agora? Eu tenho como novo teste de unidade para uso específico de API não privada.

  1. O novo teste garante que o comportamento esperado para esse uso não seja alterado sem aviso prévio, pois, se for alterado, o teste falhará.

  2. Um leitor externo pode examinar esse teste e aprender como ele deve usar e se comportar (aqui, o leitor externo inclui o meu eu futuro, pois tenho a tendência de esquecer o código um mês ou dois depois de terminar).

  3. Um novo teste é tolerante à refatoração (refatoro métodos privados? Você aposta!) O que eu fizer privateMethod, sempre desejarei testar nonPrivateMethod(true). Não importa o que eu faça privateMethod, não haverá necessidade de modificar o teste porque o método não é chamado diretamente.

Não é ruim? Pode apostar.

O que eu perco por enfraquecer a limitação de acesso

Agora imagine que, em vez de acima, eu simplesmente enfraqueci a limitação de acesso. Eu pulo o estudo do código que usa o método e prossigo diretamente com o teste que chama my exPrivateMethod. Ótimo? Não!

  1. Ganho um teste para uso específico da API não privada mencionada acima? Não: não havia teste para nonPrivateMethod(true)antes e não existe agora.

  2. Os leitores externos têm a chance de entender melhor o uso da classe? Não. - Ei, qual é o objetivo do método testado aqui? - Esqueça, é estritamente para uso interno. - Opa.

  3. É tolerante a refatoração? De jeito nenhum: o que quer que eu mude exPrivateMethod, provavelmente quebrará o teste. Renomeie, mescle com outro método, altere argumentos e teste parará de compilar. Dor de cabeça? Pode apostar!

Resumindo , aderir ao método privado me traz um aprimoramento útil e confiável nos testes de unidade. Por outro lado, o enfraquecimento das limitações de acesso "para testabilidade" só me fornece um código de teste obscuro e difícil de entender, que também corre o risco permanente de ser quebrado por qualquer refatoração menor; francamente, o que recebo parece suspeito como dívida técnica .

</rant>

mosquito
fonte
7
Nunca achei esse argumento convincente para testar métodos privados. Você refatora o código em métodos o tempo todo por razões que não têm nada a ver com a API pública de uma classe, e é uma coisa muito boa poder testar esses métodos independentemente, sem envolver nenhum outro código. Essa é a razão pela qual você refatorou, certo? Para obter uma parte da funcionalidade independente. Essa funcionalidade também deve ser testada independentemente.
Robert Harvey
A resposta do @RobertHarvey expandiu-se com um discurso retórico. "Métodos particulares são benéficos para testes de unidade ..."
gnat
Entendo o que você está dizendo sobre métodos privados e cobertura de código, mas não estava realmente defendendo a divulgação desses métodos para que você possa testá-los. Eu estava advogando ter uma maneira de testá-los independentemente, mesmo sendo privados. Você ainda pode ter seus testes públicos que tocam no método privado, se desejar. E eu posso fazer meus testes particulares.
9788 Robert Harvey
@RobertHarvey eu vejo. Acho que tudo se resume ao estilo de codificação. Na quantidade de métodos privados que normalmente produzo, e na taxa em que os refatoro, fazer testes para eles é simplesmente um luxo que não posso pagar. API não-privada é outra questão, eu tendem a ser bastante relutantes e lentos em modificá-lo #
395
Talvez você seja melhor em escrever métodos que funcionam pela primeira vez do que eu. Para mim, preciso testar o método para garantir que ele funcione primeiro antes de prosseguir, e ter que testá-lo indiretamente disparando uma série de outros métodos seria desajeitado e desajeitado.
Robert Harvey
9

A razão mais provável é: tem a ver com história. O ancestral de Java, Oak, tinha apenas três níveis de acesso: privado, protegido e público.

Exceto que private em Oak era o equivalente a package private em Java. Você pode ler a seção 4.10 da Especificação de idioma da Oak (ênfase minha):

Por padrão, todas as variáveis ​​e métodos em uma classe (incluindo construtores) são privados. Variáveis ​​e métodos privados podem ser acessados ​​apenas por métodos declarados na classe, e não por suas subclasses ou quaisquer outras classes ( exceto as classes no mesmo pacote ).

Portanto, a seu ponto, o acesso privado, como é conhecido em Java, não existia originalmente. Mas quando você tem mais do que algumas classes em um pacote, não ter private como a conhecemos agora levaria a um pesadelo de colisão de nomes (por exemplo, java.until.concurrent tem cerca de 60 classes), provavelmente o motivo pelo qual elas apresentou.

No entanto, a semântica de acesso ao pacote padrão (originalmente chamada privada) não foi alterada entre o Oak e o Java.

assilias
fonte
5

Acredito que, se o acesso padrão for usado corretamente, ele fornece testabilidade aprimorada enquanto mantém o encapsulamento. E também torna o modificador de acesso privado redundante.

Há situações em que alguém deseja duas classes separadas que são mais fortemente vinculadas do que expor tudo ao público. Isso tem idéias semelhantes de 'amigos' em C ++, onde outra classe declarada como 'amigo' de outra pessoa pode acessar seus membros privados.

Se você examinar as diversas classes de classes, como o BigInteger , encontrará vários tipos de proteção padrão de pacotes em campos e métodos (tudo o que é triângulo azul na lista). Isso permite que outras classes no pacote java.math tenham acesso mais ideal às suas entranhas para operações específicas (você pode encontrar esses métodos chamados em BigDecimal - o BigDecimal é apoiado por um BigInteger e salva a reimplementação do BigInteger novamente . um problema de proteção porque java.math é um pacote selado e nenhuma outra classe pode ser adicionada a ele.

Por outro lado, há muitas coisas que são realmente privadas, como deveriam ser. A maioria dos pacotes não é selada. Sem privado, seu colega de trabalho poderia colocar outra classe nesse pacote e acessar o campo privado (não mais) e quebrar o encapsulamento.

Note-se que o teste de unidade não era algo que se pensava quando os escopos de proteção estavam sendo construídos. O JUnit (a estrutura de teste do XUnit para Java) não foi concebida até 1997 (uma narrativa da história pode ser lida em http://www.martinfowler.com/bliki/Xunit.html ). As versões Alpha e Beta do JDK foram em 1995 e o JDK 1.0 em 1996, embora os níveis de proteção não tenham sido realmente definidos até o JDK 1.0.2 (antes era possível ter um private protectednível de proteção).

Alguns argumentam que o padrão não deve ser o nível do pacote, mas privado. Outros argumentam que não deve haver uma proteção padrão , mas tudo deve ser declarado explicitamente - alguns seguidores disso escreverão códigos como:

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

Observe o comentário lá.

A verdadeira razão pela qual a proteção no nível do pacote é o padrão provavelmente está perdida nas notas de design do Java (estive pesquisando e não consigo encontrar nenhum porquê artigos, muitas pessoas explicando a diferença, mas nenhuma dizendo "essa é a razão ").

Então, eu vou dar um palpite. E isso é tudo - um palpite.

Primeiro, as pessoas ainda estavam tentando descobrir o design da linguagem. Desde então, os idiomas aprenderam com os erros e sucessos de Java. Isso pode ser listado como um erro para não haver algo que precise ser definido para todos os campos.

  • Os designers viram uma necessidade ocasional de um relacionamento de "amigo" de C ++.
  • Os designers queriam declarações explícitas para public, e privatecomo essas têm os maiores impactos no acesso.

Assim, o privado não é padrão e, bem, tudo o resto se encaixou. O escopo padrão foi deixado como padrão. Ele pode ter sido o primeiro escopo que foi criado e no interesse da compatibilidade com versões anteriores rigorosa com código antigo, foi deixado assim.

Como eu disse, tudo isso é um palpite .


fonte
Ah, se apenas o recurso de amigo pudesse ser aplicado aos membros de maneira individual, então você poderia dizer que void someFunction () só pode ser vista pela classe X ... isso realmente reforçaria o encapsulamento!
Newlogic
Oh, espere, você pode fazer isso em C ++, precisamos disso em Java!
Newlogic
2
@ user1037729 contribui para um acoplamento mais rígido entre as duas classes - a classe com o método agora precisa conhecer as outras classes que são amigas. Isso tende a ir contra a filosofia de design que o Java fornece. O conjunto de problemas que pode ser resolvido com os amigos, mas não pacote de proteção nível não é que grande.
2

Encapsulamento é uma maneira de dizer "você não precisa pensar sobre isso".

                 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

Colocando-os em termos não técnicos.

  1. Público: todo mundo tem que pensar sobre isso.
  2. Protegido: o padrão e as subclasses precisam saber sobre isso.
  3. Por padrão, se você estiver trabalhando no pacote, você saberá sobre ele (está bem na frente dos seus olhos) também pode permitir que você aproveite.
  4. Privado, você só precisa saber sobre isso se estiver trabalhando nessa classe.
jmoreno
fonte
Eu não acho que existe tal coisa como uma classe particular ou uma classe protegida ..
Koray Tugay
@KorayTugay: consulte docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html . A classe interna é privada. Leia o último parágrafo.
jmoreno
0

Eu não acho que eles se concentraram nos testes, simplesmente porque os testes não eram muito comuns naquela época.

O que eu acho que eles queriam alcançar era o encapsulamento no nível do pacote. Como você sabe, uma classe pode ter métodos e campos internos e apenas expor parte dela através de membros públicos. Da mesma maneira, um pacote pode ter classes, métodos e campos internos e apenas expor parte dele. Se você pensa assim, um pacote possui uma implementação interna em um conjunto de classes, métodos e campos, juntamente com uma interface pública em outro conjunto (possivelmente parcialmente sobreposto) de classes, métodos e campos.

Os codificadores mais experientes que conheço pensam assim. Eles pegam o encapsulamento e o aplicam a um nível de abstração superior à classe: um pacote ou um componente, se você preferir. Parece-me razoável que os designers de Java já tenham maturidade em seus projetos, considerando o quão bem o Java ainda se mantém.

Alexander Torstling
fonte
Você está certo de que o teste automatizado não era muito comum lá atrás. Mas é um design imaturo.
Tom Hawtin - defina