En Java, C # y muchos otros lenguajes fuertemente tipados, estáticamente verificados, estamos acostumbrados a escribir código como este:
public void m1() { ... }
protected void m2() { ... }
private void m2() { ... }
void m2() { ... }
Algunos idiomas controlados dinámicamente no proporcionan palabras clave para expresar el nivel de "privacidad" de un miembro de la clase en particular y se basan en convenciones de codificación. Python, por ejemplo, antepone a miembros privados con un guión bajo:
_m(self): pass
Se puede argumentar que proporcionar tales palabras clave en idiomas dinámicamente controlados agregaría poco uso ya que solo se verifica en tiempo de ejecución.
Sin embargo, tampoco puedo encontrar una buena razón para proporcionar estas palabras clave en idiomas controlados estáticamente. Encuentro el requisito de llenar mi código con palabras clave bastante detalladas, como protected
molesto y molesto. Hasta ahora, no he estado en una situación en la que un error del compilador causado por estas palabras clave me hubiera salvado de un error. Muy al contrario, he estado en situaciones en las que un lugar equivocado protected
me impidió usar una biblioteca.
Con esto en mente, mi pregunta es:
¿La información oculta es más que una convención entre programadores utilizada para definir qué es parte de la interfaz oficial de una clase?
¿Se puede usar para asegurar que el estado secreto de una clase no sea atacado? ¿Puede la reflexión anular este mecanismo? ¿Qué haría que valiera la pena para el compilador forzar el ocultamiento de información?
fuente
Respuestas:
Estoy estudiando para la certificación de Java y un montón de eso se refiere a cómo administrar modificadores de acceso. Y tienen sentido y deben usarse correctamente.
Trabajé con Python y, durante mi viaje de aprendizaje, escuché que en Python, la convención está ahí porque las personas que trabajan con ella deben saber qué significa y cómo aplicarla. Dicho esto, en
_m(self): pass
el guión bajo me alertaría de no perder el tiempo con ese campo. ¿Pero todos seguirán esa convención? Trabajo con javascript y debo decir que no . A veces necesito verificar un problema y la razón era que la persona estaba haciendo algo que no debía hacer ...lea esta discusión sobre el guión bajo de Python
Por lo que dije, sí.
Sí, se puede usar para asegurar el estado secreto de una clase y no solo se debe usar para eso, sino para evitar que los usuarios jueguen con el estado de los objetos para cambiar sus comportamientos a algo que creen que debería ser la forma en que el objeto debería estado trabajando. Usted, como desarrollador, debe planificarlo y pensarlo y diseñar su clase de una manera que tenga sentido, de manera que su comportamiento no sea alterado. Y el compilador, como buen amigo, lo ayudará a mantener el código de la forma en que lo diseñó para hacer cumplir las políticas de acceso.
EDITADO Sí, la reflexión puede, verifique los comentarios
La última pregunta es interesante y estoy dispuesto a leer las respuestas al respecto.
fuente
El especificador de acceso "privado" no se trata del error del compilador que genera la primera vez que lo ve. En realidad, se trata de evitar que acceda a algo que aún está sujeto a cambios cuando cambia la implementación de la clase que tiene al miembro privado.
En otras palabras, no permitirle usarlo cuando todavía está funcionando le impide seguir usándolo accidentalmente cuando ya no funciona.
Como Delnan comentó a continuación, la convención de prefijo desalienta el uso accidental de miembros que están sujetos a cambios siempre que la convención se siga y se entienda correctamente. Para un usuario malintencionado (o ignorante) no hace nada para evitar que acceda a ese miembro con todas las posibles consecuencias. En idiomas con soporte incorporado para especificadores de acceso, esto no ocurre por ignorancia (error del compilador), y se destaca como un pulgar doloroso cuando es malicioso (construcciones extrañas para llegar al miembro privado).
El especificador de acceso "protegido" es una historia diferente. No piense en esto simplemente como "no del todo público" o "muy parecido a privado". "Protegido" significa que probablemente querrá usar esa funcionalidad cuando derive de la clase que contiene el miembro protegido. Los miembros protegidos son parte de la "interfaz de extensión" que usará para agregar funcionalidad a las clases existentes sin cambiar esas clases existentes.
Entonces, resumen breve:
fuente
Si está escribiendo código que será consumido por otra persona, la ocultación de información puede proporcionar una interfaz mucho más fácil de entender. "Alguien más" podría ser otro desarrollador en su equipo, desarrolladores que consumen una API que usted escribió comercialmente, o incluso su futuro yo que "simplemente no puede recordar cómo funciona la cosa". Es mucho más fácil trabajar con una clase que tiene solo 4 métodos disponibles que uno que tiene 40.
fuente
_member
oprivate member
en mi clase es insignificante, siempre y cuando el otro programador entienda que solo debe mirar apublic
los miembros no prefijados.Sugeriría que para el fondo, comience leyendo sobre invariantes de clase .
Una invariante es, para resumir una historia larga, una suposición sobre el estado de una clase que se supone que debe mantenerse durante toda la vida de una clase.
Usemos un ejemplo de C # realmente simple:
¿Que está pasando aqui?
addresses
se inicializa en la construcción de la clase.readonly
, por lo que nada desde el interior puede tocarlo después de la construcción (esto no siempre es correcto / necesario, pero es útil aquí).Send
método puede hacer una suposición queaddresses
nunca seránull
. No tiene que realizar esa verificación porque no hay forma de que se pueda cambiar el valor.Si se permitiera a otras clases escribir en el
addresses
campo (es decir, si lo fuerapublic
), esta suposición ya no sería válida. Cualquier otro método de la clase que dependa de ese campo tendría que comenzar a hacer comprobaciones nulas explícitas o arriesgarse a bloquear el programa.Entonces sí, es mucho más que una "convención"; Todos los modificadores de acceso en los miembros de la clase forman colectivamente un conjunto de suposiciones sobre cuándo y cómo se puede cambiar ese estado. Esos supuestos se incorporan posteriormente a miembros y clases dependientes e interdependientes para que los programadores no tengan que razonar sobre el estado completo del programa al mismo tiempo. La capacidad de hacer suposiciones es un elemento crítico de la gestión de la complejidad en el software.
A estas otras preguntas:
Si y no. El código de seguridad, como la mayoría de los códigos, dependerá de ciertos invariantes. Los modificadores de acceso son ciertamente útiles como señales para las personas que llaman de confianza que se supone que no deben molestar. El código malicioso no va a importar, pero el código malicioso tampoco tiene que pasar por el compilador.
Por supuesto que puede. Pero la reflexión requiere que el código de llamada tenga ese nivel de privilegio / confianza. Si está ejecutando código malicioso con plena confianza y / o privilegios administrativos, entonces ya ha perdido esa batalla.
El compilador ya lo hace cumplir. Lo mismo ocurre con el tiempo de ejecución en .NET, Java y otros entornos similares: el código de operación utilizado para llamar a un método no tendrá éxito si el método es privado. Las únicas formas de evitar esa restricción requieren un código confiable / elevado, y el código elevado siempre podría escribir directamente en la memoria del programa. Se aplica tanto como se puede aplicar sin requerir un sistema operativo personalizado.
fuente
_
establece el contrato, pero no lo impone. Eso puede hacer que sea difícil ignorar el contrato, pero no dificulta la ruptura del contrato, especialmente cuando se compara con métodos tediosos y a menudo restrictivos como Reflection. Una convención dice: "no deberías romper esto". Un modificador de acceso dice: "no puedes romper esto". Estoy no intento decir que un método es mejor que el otro - ambos están bien dependiendo de su filosofía, pero son sin embargo muy diferentes enfoques.private
/protected
modificador de acceso es como un condón. No tiene que usar uno, pero si no lo va a hacer, será mejor que esté seguro de su momento y su (s) pareja (s). No evitará un acto malicioso, y puede que ni siquiera funcione cada vez, pero definitivamente reducirá los riesgos del descuido y lo hará de manera mucho más efectiva que decir "por favor, tenga cuidado". Ah, y si tienes uno, pero no lo uses, entonces no esperes ninguna simpatía de mi parte.Ocultar información es mucho más que una simple convención; tratar de evitarlo en realidad puede romper la funcionalidad de la clase en muchos casos. Por ejemplo, es una práctica bastante común almacenar un valor en una
private
variable, exponerlo usando una propiedadprotected
opublic
del mismo tiempo, y en el captador, verificar si es nulo y hacer cualquier inicialización que sea necesaria (es decir, carga diferida). O almacene algo en unaprivate
variable, exponga usando una propiedad y, en el setter, verifique si el valor cambió y se disparóPropertyChanging
/PropertyChanged
eventos. Pero sin ver la implementación interna, nunca sabrías todo lo que sucede detrás de escena.fuente
La ocultación de información evolucionó a partir de una filosofía de diseño de arriba hacia abajo. Python ha sido llamado un lenguaje ascendente .
La ocultación de información se aplica bien a nivel de clase en Java, C ++ y C #, por lo que no es realmente una convención a este nivel. Es muy fácil convertir una clase en un "recuadro negro", con interfaces públicas y detalles ocultos (privados).
Como señaló, en Python corresponde a los programadores seguir la convención de no usar lo que está destinado a estar oculto, ya que todo es visible.
Incluso con Java, C ++ o C #, en algún momento la ocultación de información se convierte en una convención. No hay controles de acceso en los niveles más altos de abstracción involucrados en arquitecturas de software más complejas. Por ejemplo, en Java puede encontrar el uso de ".internal". nombres de paquetes Esto es puramente una convención de nomenclatura porque no es fácil hacer cumplir este tipo de ocultación de información solo a través de la accesibilidad del paquete.
Un lenguaje que se esfuerza por definir el acceso formalmente es Eiffel. Este artículo señala algunas otras debilidades que ocultan información de lenguajes como Java.
Antecedentes: David Parnas propuso el ocultamiento de información en 1971 . Señala en ese artículo que el uso de información sobre otros módulos puede "aumentar desastrosamente la conectividad de la estructura del sistema". Según esta idea, la falta de ocultación de información puede conducir a sistemas estrechamente acoplados que son difíciles de mantener. Él continúa con:
fuente
Ruby y PHP lo tienen y lo hacen cumplir en tiempo de ejecución.
El punto de ocultar información es en realidad mostrar información. Al "ocultar" los detalles internos, el propósito se hace evidente desde una perspectiva externa. Hay idiomas que abarcan esto. En Java, el acceso predeterminado es el paquete interno, en haXe a protegido. Usted los declara explícitamente públicos para exponerlos.
El objetivo de esto es hacer que sus clases sean fáciles de usar al exponer solo una interfaz altamente coherente. Desea que el resto esté protegido, para que ningún tipo inteligente venga y se meta con su estado interno para engañar a su clase para que haga lo que quiere.
Además, cuando los modificadores de acceso se aplican en tiempo de ejecución, puede usarlos para aplicar un cierto nivel de seguridad, pero no creo que esta sea una solución particularmente buena.
fuente
pydoc
elimina_prefixedMembers
de la interfaz. Las interfaces desordenadas no tienen nada que ver con la elección de una palabra clave verificada por el compilador.Los modificadores de acceso definitivamente pueden hacer algo que la convención no puede hacer.
Por ejemplo, en Java no puede acceder a un miembro / campo privado a menos que use la reflexión.
Por lo tanto, si escribo la interfaz para un complemento y denego correctamente los derechos para modificar campos privados a través de la reflexión (y para configurar el administrador de seguridad :)), puedo enviar algunos objetos a funciones implementadas por cualquier persona y saber que no puede acceder a sus campos privados.
Por supuesto, puede haber algunos errores de seguridad que le permitirían superar esto, pero esto no es filosóficamente importante (pero en la práctica definitivamente lo es).
Si el usuario ejecuta mi interfaz en su entorno, tiene el control y, por lo tanto, puede eludir los modificadores de acceso.
fuente
No es tanto para salvar a los autores de aplicaciones de los errores como para permitir que los autores de la biblioteca decidan qué partes de su implementación se comprometen a mantener.
Si tengo una biblioteca
Es posible que desee reemplazar
foo
la implementación y posiblemente cambiarfooHelper
de manera radical. Si un grupo de personas ha decidido usarlo afooHelper
pesar de todas las advertencias en mi documentación, es posible que no pueda hacerlo.private
permite a los autores de bibliotecas dividir las bibliotecas en métodos de tamaño manejable (yprivate
clases auxiliares) sin temor a que se vean obligados a mantener esos detalles internos durante años.En una nota al margen, en Java
private
no se aplica por el compilador, sino por el verificador de bytecode de Java .En Java, no solo la reflexión puede anular este mecanismo. Hay dos tipos de
private
en Java. El tipo deprivate
eso evita que una clase externa acceda a losprivate
miembros de otra clase externa que es verificada por el verificador de bytecode, pero tambiénprivate
es utilizada por una clase interna a través de un método de acceso sintético privado de paquete como enComo la clase
B
(realmente llamadaC$B
) usai
, el compilador crea un métodoB
de acceso sintético que permite accederC.i
de una manera que supera el verificador de bytecode. Desafortunadamente, dadoClassLoader
que le permite crear una clase a partir de unabyte[]
, es bastante sencillo llegar a las partes privadas que seC
han expuesto a las clases internas creando una nueva clase enC
el paquete que es posible siC
el jar no se ha sellado.La
private
aplicación adecuada requiere coordinación entre los cargadores de clases, el verificador de bytecode y la política de seguridad que puede evitar el acceso reflexivo a los privados.Si. La "descomposición segura" es posible cuando los programadores pueden colaborar mientras cada uno preserva las propiedades de seguridad de sus módulos. No tengo que confiar en el autor de otro módulo de código para no violar las propiedades de seguridad de mi módulo.
Los lenguajes de Capacidades de Objetos como Joe-E usan la ocultación de información y otros medios para hacer posible la descomposición segura:
El documento vinculado desde esa página ofrece un ejemplo de cómo la
private
aplicación hace posible la descomposición segura.fuente
La ocultación de información es una de las principales preocupaciones del buen diseño de software. Echa un vistazo a cualquiera de los documentos de Dave Parnas de finales de los 70. Básicamente, si no puede garantizar que el estado interno de su módulo sea consistente, no puede garantizar nada sobre su comportamiento. Y la única forma en que puede garantizar su estado interno es mantenerlo privado y solo permitir que se modifique por los medios que usted proporcione.
fuente
Al hacer que la protección sea parte del lenguaje, obtienes algo: seguridad razonable.
Si hago que una variable sea privada, tengo la seguridad razonable de que solo será tocada por el código dentro de esa clase o declarada explícitamente como amigos de esa clase. El alcance del código que podría tocar razonablemente ese valor es limitado y está explícitamente definido.
¿Ahora hay formas de evitar la protección sintáctica? Absolutamente; la mayoría de los idiomas los tienen. En C ++, siempre puede convertir la clase a otro tipo y pinchar en sus bits. En Java y C #, puede reflejarse en él. Etcétera.
Sin embargo, hacer esto es difícil . Es obvio que estás haciendo algo que no deberías estar haciendo. No puede hacerlo por accidente (fuera de las escrituras salvajes en C ++). Debes pensar voluntariamente: "Voy a tocar algo que mi compilador me dijo que no hiciera". Debes voluntariamente hacer algo irracional .
Sin protección sintáctica, un programador puede arruinar accidentalmente las cosas. Tienes que enseñarle al usuario una convención, y ellos deben seguir esa convención cada vez . Si no lo hacen, el mundo se vuelve muy inseguro.
Sin protección sintáctica, la responsabilidad recae en las personas equivocadas: las muchas personas que usan la clase. Deben seguir la convención o se producirá una maldad no especificada. Rasca eso: puede ocurrir una maldad no especificada .
No hay nada peor que una API donde, si haces lo incorrecto, todo podría funcionar de todos modos. Eso proporciona una falsa seguridad al usuario de que ha hecho lo correcto, que todo está bien, etc.
¿Y los nuevos usuarios de ese idioma? No solo tienen que aprender y seguir la sintaxis real (aplicada por el compilador), ahora deben seguir esta convención (aplicada por sus pares en el mejor de los casos). Y si no lo hacen, entonces nada malo puede suceder. ¿Qué sucede si un programador no entiende por qué existe la convención? ¿Qué sucede cuando él dice "atorníllelo" y solo golpea tus partes privadas? ¿Y qué piensa él si todo sigue funcionando?
Él piensa que la convención es estúpida. Y no lo seguirá nunca más. Y les dirá a todos sus amigos que no se molesten también.
fuente