¿Por qué Java hizo que el acceso al paquete sea predeterminado?

26

Estoy haciendo esta pregunta porque creo que lo hicieron por una muy buena razón y que la mayoría de las personas no lo usan correctamente, de todos modos, según mi experiencia en la industria hasta ahora. Pero si mi teoría es cierta, entonces no estoy seguro de por qué incluyeron el modificador de acceso privado ...

Creo que si el acceso predeterminado se usa correctamente, proporciona una capacidad de prueba mejorada mientras se mantiene la encapsulación. Y también hace que el modificador de acceso privado sea redundante.

El modificador de acceso predeterminado se puede usar para proporcionar el mismo efecto mediante el uso de un paquete único para métodos que deben ocultarse del resto del mundo, y lo hace sin comprometer la capacidad de prueba, ya que los paquetes en una carpeta de prueba, con el mismo capaz de acceder a todos los métodos predeterminados declarados en una carpeta de origen.

Creo que es por eso que Java usa el acceso a paquetes como 'predeterminado'. Pero no estoy seguro de por qué también incluyeron acceso privado, estoy seguro de que hay un caso de uso válido ...

nueva lógica
fuente
Relevante con respecto a los métodos privados de prueba de unidad discutidos previamente; cómo-haces-tu-unidad-prueba-métodos-privados . Responder; no deberías
Richard Tingle

Respuestas:

25

Supongo que tenían una buena idea de lo que haría un programador promedio. Y por programador promedio me refiero a uno que no es realmente bueno en programación, pero que consigue el trabajo de todos modos porque no hay suficientes buenos y son caros.

Si hacen que el acceso "público" sea el predeterminado, la mayoría de los programadores nunca se molestarán en usar otra cosa. El resultado sería una gran cantidad de código de espagueti en todas partes. (Porque si técnicamente se le permite llamar a cualquier cosa desde cualquier lugar, ¿por qué molestarse en encapsular alguna lógica dentro de un método?)

Hacer "privado" el acceso predeterminado solo sería un poco mejor. La mayoría de los programadores principiantes simplemente inventarían (y compartirían en sus blogs) una regla general que dice "necesitas escribir 'público' en todas partes", y luego se quejarían de por qué Java es tan malo que los obliga a escribir "público" en todas partes. Y también producirían mucho código de espagueti.

El acceso al paquete es algo que permite a los programadores usar sus técnicas de programación descuidadas mientras escriben código dentro de un paquete, pero luego tienen que reconsiderarlo al hacer más paquetes. Es un compromiso entre las buenas prácticas comerciales y la fea realidad. Me gusta: escriba un código de espagueti, si insiste, pero deje el feo desorden dentro del paquete; al menos cree algunas interfaces más agradables entre los paquetes.

Probablemente también haya otras razones, pero no subestimaría esta.

Viliam Búr
fuente
14

El acceso predeterminado no hace que el modificador de acceso privado sea redundante.

La posición de los diseñadores de idiomas al respecto se refleja en el tutorial oficial: Control del acceso a los miembros de una clase y es bastante claro (para su conveniencia, declaración relevante en la cita en negrita ):

Consejos para elegir un nivel de acceso:

Si otros programadores usan tu clase, debes asegurarte de que no puedan ocurrir errores por mal uso. Los niveles de acceso pueden ayudarlo a hacer esto.

  • Utilice el nivel de acceso más restrictivo que tenga sentido para un miembro en particular. Use privado a menos que tenga una buena razón para no hacerlo.
  • Evite los campos públicos excepto las constantes. (Muchos de los ejemplos en el tutorial usan campos públicos. Esto puede ayudar a ilustrar algunos puntos de manera concisa, pero no se recomienda para el código de producción). Los campos públicos tienden a vincularlo a una implementación particular y limitan su flexibilidad para cambiar su código.

Su apelación a la capacidad de prueba como justificación para descartar por completo el modificador privado es incorrecta, como lo demuestran, por ejemplo, las respuestas en Nuevo en TDD. ¿Debo evitar los métodos privados ahora?

Por supuesto, puede tener métodos privados y, por supuesto, puede probarlos.

O hay alguna forma de hacer que el método privado se ejecute, en cuyo caso puede probarlo de esa manera, o no hay forma de hacer que el privado se ejecute, en cuyo caso: ¿por qué diablos está tratando de probarlo? eliminar la maldita cosa ...


La posición de los diseñadores de idiomas sobre el propósito y el uso del acceso a nivel de paquete se explica en otro tutorial oficial, Creación y uso de paquetes, y no tiene nada en común con la idea de descartar modificadores privados (para su conveniencia, declaración relevante en la cita en negrita ) :

Debe agrupar estas clases y la interfaz en un paquete por varias razones, incluidas las siguientes:

  • Usted y otros programadores pueden determinar fácilmente que estos tipos están relacionados ...
  • Puede permitir que los tipos dentro del paquete tengan acceso sin restricciones entre sí y aún así restringir el acceso para los tipos fuera del paquete ...

<despotricar "Creo que escuché suficientes quejidos. Supongo que es hora de decir fuerte y claro ...">

Los métodos privados son beneficiosos para las pruebas unitarias.

La nota a continuación supone que está familiarizado con la cobertura del código . Si no, tómese un tiempo para aprender, ya que es bastante útil para aquellos interesados ​​en las pruebas unitarias y en las pruebas.

Muy bien, entonces tengo ese método privado y pruebas unitarias, y el análisis de cobertura me dice que hay una brecha, mi método privado no está cubierto por las pruebas. Ahora...

¿Qué gano al mantenerlo privado?

Dado que el método es privado, la única forma de proceder es estudiar el código para aprender cómo se usa a través de una API no privada. Por lo general, dicho estudio revela que la razón de la brecha es que falta un escenario de uso particular en las pruebas.

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

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

En aras de la integridad, otras razones (menos frecuentes) para tales brechas de cobertura podrían ser errores en la especificación / diseño. No voy a profundizar en esto aquí, para simplificar las cosas; basta con decir que si debilita la limitación de acceso "solo para hacer que el método sea comprobable", perderá la oportunidad de saber que estos errores existen.

Bien, para arreglar la brecha, agrego una prueba unitaria para el escenario faltante, repito el análisis de cobertura y verifico que la brecha haya desaparecido. ¿Qué tengo ahora? Tengo una nueva prueba de unidad para el uso específico de API no privada.

  1. La nueva prueba asegura que el comportamiento esperado para este uso no cambiará sin previo aviso ya que si cambia, la prueba fallará.

  2. Un lector externo puede analizar esta prueba y aprender cómo se supone que debe usar y comportarse (aquí, el lector externo incluye mi futuro yo, ya que tiendo a olvidar el código un mes o dos después de que lo haya terminado).

  3. La nueva prueba es tolerante a la refactorización (¿refactorizo ​​los métodos privados? ¡Apuesto!) Lo que sea que haga privateMethod, siempre querré probar nonPrivateMethod(true). No importa lo que haga privateMethod, no será necesario modificar la prueba porque el método no se invoca directamente.

¿No está mal? Usted apuesta.

¿Qué pierdo al debilitar la limitación de acceso?

Ahora imagine que en lugar de lo anterior, simplemente debilito la limitación de acceso. Me salto el estudio del código que usa el método y procedo directamente con la prueba que invoca mi exPrivateMethod. ¿Excelente? ¡No!

  1. ¿Obtengo una prueba para el uso específico de la API no privada mencionada anteriormente? No: no había una prueba para nonPrivateMethod(true)antes, y no hay tal prueba ahora.

  2. ¿Los lectores externos tienen la oportunidad de comprender mejor el uso de la clase? No. "- Oye, ¿cuál es el propósito del método probado aquí? - Olvídalo, es estrictamente para uso interno. - Oops".

  3. ¿Es tolerante refactorizar? De ninguna manera: lo que sea que cambie exPrivateMethod, probablemente romperá la prueba. Cambiar el nombre, fusionarse con algún otro método, cambiar los argumentos y la prueba simplemente dejará de compilarse. ¿Dolor de cabeza? Usted apuesta!

En resumen , seguir con el método privado me brinda una mejora útil y confiable en las pruebas unitarias. Por el contrario, el debilitamiento de las limitaciones de acceso "para la capacidad de prueba" solo me da una pieza oscura y difícil de entender del código de prueba, que además está en riesgo permanente de ser roto por cualquier refactorización menor; francamente, lo que obtengo se parece sospechosamente a una deuda técnica .

</rant>

mosquito
fuente
77
Nunca he encontrado convincente ese argumento para probar métodos privados. Refactoriza el código en métodos todo el tiempo por razones que no tienen nada que ver con la API pública de una clase, y es muy bueno poder probar esos métodos de forma independiente, sin involucrar ningún otro código. Esa es la razón por la que refactorizó, ¿verdad? Para obtener una pieza de funcionalidad independiente. Esa funcionalidad también debe ser comprobable de forma independiente.
Robert Harvey
La respuesta de @RobertHarvey se expandió con una queja que abordaba esto. "Los métodos privados son beneficiosos para las pruebas unitarias ..."
mosquito
Veo lo que está diciendo sobre los métodos privados y la cobertura del código, pero en realidad no estaba abogando por hacer públicos esos métodos para que pueda probarlos. Estaba abogando por tener una forma de probarlos de forma independiente a pesar de que son privados. Todavía puede tener sus pruebas públicas que toquen el método privado, si así lo desea. Y puedo tener mis pruebas privadas.
Robert Harvey
@RobertHarvey Ya veo. Supongo que se reduce al estilo de codificación. Debido a la cantidad de métodos privados que suelo producir, y al ritmo en que los refactorizo, realizar pruebas para ellos es simplemente un lujo que no puedo permitirme. La API no privada es otra cuestión, tiendo a ser bastante reacio y lento para modificarla
mosquito
Quizás eres mejor escribiendo métodos que funcionan la primera vez que yo. Para mí, necesito probar el método para asegurarme de que funcione primero antes de poder seguir adelante, y tener que probarlo indirectamente disparando una serie de otros métodos sería torpe e incómodo.
Robert Harvey
9

La razón más probable es: tiene que ver con la historia. El antepasado de Java, Oak, solo tenía tres niveles de acceso: privado, protegido, público.

Excepto que privado en Oak era el equivalente de paquete privado en Java. Puede leer la sección 4.10 de la Especificación del idioma de Oak (énfasis mío):

Por defecto, todas las variables y métodos de una clase (incluidos los constructores) son privados. Solo se puede acceder a las variables y métodos privados mediante métodos declarados en la clase, y no mediante sus subclases o cualquier otra clase ( excepto las clases en el mismo paquete ).

Entonces, según su punto de vista, el acceso privado, como se conoce en Java, no estaba allí originalmente. Pero cuando tiene más de unas pocas clases en un paquete, no tener clases privadas como las conocemos ahora conduciría a una pesadilla de colisión de nombres (por ejemplo, java.until.concurrent tiene cerca de 60 clases), lo cual es probablemente la razón por la cual lo introdujo

Sin embargo, la semántica predeterminada de acceso al paquete (originalmente llamada privada) no se modificó entre Oak y Java.

asilias
fuente
5

Creo que si el acceso predeterminado se usa correctamente, proporciona una capacidad de prueba mejorada mientras se mantiene la encapsulación. Y también hace que el modificador de acceso privado sea redundante.

Hay situaciones en las que uno desearía dos clases separadas que estén más vinculadas que exponer todo al público. Esto tiene ideas similares de 'amigos' en C ++ donde otra clase que se declara como 'amigo' de otra puede acceder a sus miembros privados.

Si miras las entrañas de clases como BigInteger , encontrará una serie de protección predeterminada de paquetes en campos y métodos (todo lo que es un triángulo azul en la lista). Esto permite que otras clases en el paquete java.math tengan un acceso más óptimo a sus entrañas para operaciones específicas (puede encontrar estos métodos llamados en BigDecimal - BigDecimal está respaldado por un BigInteger y ahorra volver a implementar BigInteger nuevamente . Que estos son niveles de paquete no ' Es un problema de protección porque java.math es un paquete sellado y no se le pueden agregar otras clases.

Por otro lado, hay muchas cosas que de hecho son privadas, como deberían ser. La mayoría de los paquetes no están sellados. Sin privado, su compañero de trabajo podría poner otra clase en ese paquete y acceder al campo privado (ya no) y romper la encapsulación.

Cabe destacar que las pruebas unitarias no eran algo en lo que se pensaba cuando se construían los ámbitos de protección. JUnit (el marco de prueba de XUnit para Java) no se concibió hasta 1997 (una narración de la historia se puede leer en http://www.martinfowler.com/bliki/Xunit.html ). Las versiones Alpha y Beta del JDK fueron en 1995 y JDK 1.0 fue en 1996, aunque los niveles de protección realmente no se concretaron hasta JDK 1.0.2 (antes de eso, podría tener un private protectednivel de protección).

Algunos argumentarían que el valor predeterminado no debería ser el nivel del paquete, sino privado. Otros argumentan que no debería haber una protección predeterminada , pero todo debería estar explícitamente establecido; algunos seguidores de esto escribirán código como:

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

Tenga en cuenta el comentario allí.

La verdadera razón por la cual la protección a nivel de paquete es la predeterminada es probable que se pierda en las notas de diseño de Java (he estado cavando y no puedo encontrar ningún artículo de por qué , mucha gente explica la diferencia, pero ninguno dice "esta es la razón ").

Así que adivinaré. Y eso es todo lo que es, una suposición.

En primer lugar, la gente todavía intentaba descifrar el diseño del lenguaje. Desde entonces, los idiomas han aprendido de los errores y éxitos de Java. Esto podría enumerarse como un error al no tener algo que deba definirse para todos campos.

  • Los diseñadores vieron una necesidad ocasional de una relación 'amiga' de C ++.
  • Los diseñadores querían declaraciones explícitas publicy, privatedado que tienen el mayor impacto en el acceso.

Por lo tanto, privado no es predeterminado y bueno, todo lo demás encajó. El alcance predeterminado se dejó como predeterminado. Se puede haber sido la primera ámbito que se ha creado y en el interés de rigurosa compatibilidad con código antiguo, que quedaba de esa manera.

Como dije, todo esto es una suposición .


fuente
Ah, si solo la característica de amigo se pudiera aplicar a los miembros de forma individual, por lo que se podría decir que anular someFunction () solo se puede ver en la clase X ... ¡eso realmente reforzaría la encapsulación!
newlogic
Oh, espera, puedes hacer esto en C ++, ¡necesitamos esto en Java!
newlogic
2
@ user1037729 crea un acoplamiento más estrecho entre las dos clases: la clase con el método ahora necesita saber de las otras clases que son sus amigos. Esto tiende a ir en contra de la filosofía de diseño que proporciona Java. El conjunto de problemas que se pueden resolver con amigos, pero no paquete de protección de nivel no es que grande.
2

La encapsulación es una forma de decir "no tienes que pensar en esto".

                 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

Poniendo esto en términos no técnicos.

  1. Público: todos tienen que pensarlo.
  2. Protegido: el valor predeterminado y las subclases deben saberlo.
  3. Por defecto, si está trabajando en el paquete, lo sabrá (está justo en frente de sus ojos) también podría permitirle aprovecharlo.
  4. Privado, solo necesita saber sobre esto si está trabajando en esta clase.
jmoreno
fuente
No creo que haya una clase privada o una clase protegida ..
Koray Tugay
@KorayTugay: mira docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html . La clase interna es privada. Lee el último párrafo.
jmoreno
0

No creo que se hayan centrado en las pruebas, simplemente porque las pruebas no eran muy comunes en ese entonces.

Lo que sí creo que querían lograr era la encapsulación en el nivel del paquete. Como sabe, una clase puede tener métodos y campos internos y solo exponer algunos de ellos a través de miembros públicos. Del mismo modo, un paquete puede tener clases internas, métodos y campos, y solo exponer algunos de ellos. Si piensa de esta manera, un paquete tiene una implementación interna en un conjunto de clases, métodos y campos, junto con una interfaz pública en otro conjunto (posiblemente superpuesto) de clases, métodos y campos.

Los codificadores más experimentados que conozco piensan de esta manera. Toman la encapsulación y la aplican a un nivel de abstracción más alto que la clase: un paquete o un componente si lo desea. Me parece razonable que los diseñadores de Java ya fueran tan maduros en sus diseños, considerando lo bien que Java todavía se mantiene.

Alexander Torstling
fuente
Tienes razón en que las pruebas automatizadas no eran muy comunes allí. Pero es un diseño inmaduro.
Tom Hawtin - tackline