¿Por qué Clean Code sugiere evitar las variables protegidas?

168

Clean Code sugiere evitar variables protegidas en la sección "Distancia vertical" del capítulo "Formateo":

Los conceptos que están estrechamente relacionados deben mantenerse verticalmente cerca uno del otro. Claramente, esta regla no funciona para conceptos que pertenecen a archivos separados. Pero los conceptos estrechamente relacionados no deben separarse en archivos diferentes a menos que tenga una muy buena razón. De hecho, esta es una de las razones por las que se deben evitar las variables protegidas .

¿Cuál es el razonamiento?

Matsemann
fuente
77
mostrar un ejemplo donde usted cree que necesita es
mosquito
63
No tomaría mucho en ese libro como evangelio.
Plataforma
40
@Rig, estás bordeando la blasfemia en estas partes con un comentario como ese.
Ryathal
40
Estoy bien con eso. He leído el libro y puedo tener mi propia opinión al respecto.
Plataforma
10
He leído el libro y, aunque estoy de acuerdo con la mayoría de lo que hay allí, tampoco diría que lo considero evangelio. Creo que cada situación es diferente ... y poder cumplir con las pautas exactas representadas en el libro es bastante difícil para muchos proyectos.
Simon Whitehead

Respuestas:

277

Las variables protegidas deben evitarse porque:

  1. Tienden a conducir a problemas de YAGNI . A menos que tenga una clase descendiente que realmente haga cosas con el miembro protegido, hágalo privado.
  2. Tienden a conducir a problemas de LSP . Las variables protegidas generalmente tienen cierta invariancia intrínseca asociada a ellas (o de lo contrario serían públicas). Los herederos deben mantener esas propiedades, que las personas pueden arruinar o violar deliberadamente.
  3. Tienden a violar OCP . Si la clase base hace demasiados supuestos sobre el miembro protegido, o el heredero es demasiado flexible con el comportamiento de la clase, puede llevar a que esa extensión modifique el comportamiento de la clase base.
  4. Tienden a conducir a la herencia por extensión más que por composición. Esto tiende a conducir a un acoplamiento más estricto, más violaciones de SRP , pruebas más difíciles y una serie de otras cosas que caen dentro de la discusión de 'composición del favor sobre la herencia'.

Pero como puede ver, todos estos son 'tienden a'. A veces, un miembro protegido es la solución más elegante. Y las funciones protegidas tienden a tener menos problemas. Pero hay una serie de cosas que hacen que se traten con cuidado. Con cualquier cosa que requiera ese tipo de cuidado, las personas cometerán errores y en el mundo de la programación eso significa errores y problemas de diseño.

Telastyn
fuente
Gracias por muchas buenas razones. Sin embargo, el libro lo menciona específicamente en un contexto de formato y distancia vertical, es esa parte de la que me pregunto.
Matsemann
2
Parece que el tío Bob se refiere a la distancia vertical cuando alguien se refiere a un miembro protegido entre clases, y no en la misma clase.
Robert Harvey
55
@ Matsemann, claro. Si recuerdo el libro con precisión, esa sección se centra en la legibilidad y la capacidad de descubrimiento del código. Si las variables y funciones funcionan en un concepto, deben estar cerca del código. Los miembros protegidos serán utilizados por tipos derivados, que necesariamente no pueden estar cerca del miembro protegido ya que están en un archivo completamente diferente.
Telastyn
2
@ steve314 No puedo imaginar un escenario en el que la clase base y todos sus herederos solo vivan en un archivo. Sin un ejemplo, me inclino a pensar que es un abuso de la herencia o una mala abstracción.
Telastyn
3
YAGNI = No lo vas a necesitar LSP = Principio de sustitución de Liskov OCP = Principio de apertura / cierre SRP = Principio de responsabilidad única
Bryan Glazer
54

Es realmente la misma razón por la que evitas los globales, solo que a menor escala. Es porque es difícil encontrar en todas partes una variable que se está leyendo, o peor aún, escrita, y es difícil hacer que el uso sea consistente. Si el uso es limitado, como escribir en un lugar obvio en la clase derivada y leer en un lugar obvio en la clase base, las variables protegidas pueden hacer que el código sea más claro y conciso. Si tiene la tentación de leer y escribir una variable willy-nilly en varios archivos, es mejor encapsularla.

Karl Bielefeldt
fuente
26

No he leído el libro, pero puedo entender lo que el tío Bob quería decir.

Si te pones protectedalgo, eso significa que una clase puede heredarlo. Pero se supone que las variables miembro pertenecen a la clase en la que están contenidas; Esto es parte de la encapsulación básica. Poner protecteden una variable miembro rompe la encapsulación porque ahora una clase derivada tiene acceso a los detalles de implementación de la clase base. Es el mismo problema que ocurre cuando haces una variable publicen una clase ordinaria.

Para corregir el problema, puede encapsular la variable en una propiedad protegida, así:

protected string Name
{
    get { return name; }
    private set { name = value; }
}

Esto permite nameestablecer de forma segura desde una clase derivada utilizando un argumento constructor, sin exponer detalles de implementación de la clase base.

Robert Harvey
fuente
Creó una propiedad pública con setter protegido. El campo protegido debe resolverse con una propiedad protegida con setter privado.
Andy
2
+1 ahora. Setter también podría estar protegido, supongo, pero los identificadores de puntos que la base puede aplicar si el nuevo valor es válido o no.
Andy
Solo para ofrecer un punto de vista opuesto: protejo todas mis variables en una clase base. Si solo se puede acceder a ellos a través de una interfaz pública, entonces no debería ser una clase derivada, solo debería contener un objeto de clase base. Pero también evito jerarquías de más de 2 clases de profundidad
Martin Beckett
2
Hay una gran diferencia entre protectedy publicmiembros. Si BaseTypeexpone a un miembro público, eso implica que todos los tipos derivados deben tener el mismo miembro funcionando de la misma manera, ya que una DerivedTypeinstancia se puede pasar al código que recibe una BaseTypereferencia y espera usar ese miembro. Por el contrario, si BaseTypeexpone a un protectedmiembro, DerivedTypepuede esperar acceder a ese miembro base, pero baseno puede ser otra cosa que a BaseType.
supercat
18

El argumento del tío Bob es principalmente de distancia: si tiene un concepto que es importante para una clase, agrupe el concepto junto con la clase en ese archivo. No separados en dos archivos en el disco.

Las variables miembro protegidas se encuentran dispersas en dos lugares y, de alguna manera, parece magia. Hace referencia a esta variable, pero no está definida aquí ... ¿dónde está definida? Y así comienza la caza. Mejor evitar por protectedcompleto, su argumento.

Ahora no creo que esa regla deba ser obedecida al pie de la letra todo el tiempo (como: no debes usarla protected). Mire el espíritu de lo que está llegando ... agrupe cosas relacionadas en un solo archivo; use técnicas y características de programación para hacer eso. Le recomendaría que no analice en exceso y se quede atrapado en los detalles sobre esto.

J. Polfer
fuente
9

Algunas muy buenas respuestas ya están aquí. Pero una cosa para agregar:

En la mayoría de los lenguajes OO modernos, es un buen hábito (en Java AFAIK es necesario) poner cada clase en su propio archivo (por favor, no entienda acerca de C ++, donde a menudo tiene dos archivos).

Por lo tanto, es prácticamente imposible mantener las funciones en una clase derivada accediendo a una variable protegida de una clase base "verticalmente cercana" en código fuente a la definición de la variable. Pero idealmente deberían mantenerse allí, ya que las variables protegidas están destinadas al uso interno, lo que hace que las funciones que las acceden a menudo sean "un concepto estrechamente relacionado".

Doc Brown
fuente
1
No en Swift Curiosamente, Swift no tiene "protegido" pero tiene "fileprivate" que reemplaza tanto "protegido" como "amigo" de C ++.
gnasher729
@ gnasher729 - por supuesto, Swift tiene mucho Smalltalk en su herencia. Smalltalk no tiene nada más que variables privadas y métodos públicos. Y privado en Smalltalk significa privado: dos objetos incluso de la misma clase no pueden acceder a las variables del otro.
Julio
4

La idea básica es que un "campo" (variable de nivel de instancia) que se declara como protegido es probablemente más visible de lo que debe ser, y menos "protegido" de lo que quisiera. No hay un modificador de acceso en C / C ++ / Java / C # que sea equivalente a "accesible solo para las clases secundarias dentro del mismo ensamblado", lo que le permite definir sus propios hijos que pueden acceder al campo en su ensamblaje, pero no permitir que los niños creados en otros ensamblados tengan el mismo acceso; C # tiene modificadores internos y protegidos, pero combinarlos hace que el acceso sea "interno o protegido", no "interno y protegido". Por lo tanto, cualquier niño puede acceder a un campo protegido, ya sea que usted haya escrito ese niño o alguien más. Protegido es, por lo tanto, una puerta abierta a un hacker.

Además, los campos, por su definición, prácticamente no tienen validación inherente a su cambio. en C #, puede hacer uno de solo lectura, lo que hace que los tipos de valor sean efectivamente constantes y los tipos de referencia no puedan reinicializarse (pero aún son muy mutables), pero eso es todo. Como tal, incluso protegido, sus hijos (en los que no puede confiar) tienen acceso a este campo y pueden configurarlo como algo no válido, haciendo que el estado del objeto sea inconsistente (algo que debe evitarse).

La forma aceptada de trabajar con campos es hacerlos privados y acceder a ellos con una propiedad y / o un método getter y setter. Si todos los consumidores de la clase necesitan el valor, haga público el captador (al menos). Si solo los niños lo necesitan, proteja al captador.

Otro enfoque que responde a la pregunta es preguntarse a sí mismo; ¿Por qué el código en un método secundario necesita la capacidad de modificar mis datos de estado directamente? ¿Qué dice eso sobre ese código? Ese es el argumento de la "distancia vertical" en su cara. Si hay un código en un elemento secundario que debe alterar directamente el estado primario, ¿tal vez ese código debería pertenecer al elemento primario en primer lugar?

KeithS
fuente
4

Es una discusión interesante.

Para ser sincero, las buenas herramientas pueden mitigar muchos de estos problemas "verticales". De hecho, en mi opinión, la noción de "archivo" es en realidad algo que dificulta el desarrollo de software, algo en lo que está trabajando el proyecto Light Table (http://www.chris-granger.com/2012/04/12/light- table --- a-new-ide-concept /).

Saque los archivos de la imagen, y el alcance protegido se vuelve mucho más atractivo. El concepto protegido se vuelve más claro en idiomas como SmallTalk, donde cualquier herencia simplemente extiende el concepto original de una clase. Debería hacer todo, y tener todo, que hizo la clase padre.

En mi opinión, las jerarquías de clase deberían ser lo más superficiales posible, con la mayoría de las extensiones provenientes de la composición. En tales modelos, no veo ninguna razón para que las variables privadas no estén protegidas. Si la clase extendida debería representar una extensión que incluye todo el comportamiento de la clase padre (lo cual creo que debería, y por lo tanto creo que los métodos privados son inherentemente malos), ¿por qué la clase extendida no debería representar también el almacenamiento de dichos datos?

Para decirlo de otra manera: en cualquier caso en el que creo que una variable privada se adaptaría mejor que una protegida, la herencia no es la solución al problema en primer lugar.

spronkey
fuente
0

Como dice allí, esta es solo una de las razones, es decir, el código lógicamente vinculado debe colocarse dentro de las entidades físicas vinculadas (archivos, paquetes y otros). En una escala más pequeña, esta es la misma razón por la que no coloca una clase que realiza consultas DB dentro del paquete con clases que muestran la interfaz de usuario.

Sin embargo, la razón principal por la que no se recomiendan variables protegidas es porque tienden a romper la encapsulación. Las variables y los métodos deben tener la visibilidad más limitada posible; para más información, consulte Java eficaz de Joshua Bloch , elemento 13: "Minimice la accesibilidad de clases y miembros".

Sin embargo, no debes tomar todo este ad litteram, solo como una línea de guild; Si las variables protegidas son muy malas, en primer lugar no se habrían colocado dentro del lenguaje. Un lugar razonable para los campos protegidos en mi humilde opinión es dentro de la clase base desde un marco de prueba (las clases de prueba de integración lo extienden).

m3th0dman
fuente
1
No asuma que porque un idioma lo permite, está bien hacerlo. No estoy seguro de que realmente haya una buena razón para permitir campos protegidos; en realidad no los permitimos en mi empleador.
Andy
2
@Andy No has leído lo que dije; Dije que no se recomiendan los campos protegidos y las razones de esto. Claramente, hay escenarios en los que se necesitan variables protegidas; es posible que desee un valor final constante solo en las subclases.
m3th0dman
Es posible que desee volver a redactar parte de su publicación, ya que parece que usa variables y campos de manera intercambiable y hace explícito que consideraría que las cosas como consts protegidos son una excepción.
Andy
3
@Andy Es bastante obvio que, como me refería a variables protegidas, no estaba hablando de variables locales o de parámetros, sino de campos. También mencioné un escenario donde las variables protegidas no constantes son útiles; En mi opinión, está mal que una empresa aplique la forma en que los programadores escriben el código.
m3th0dman
1
Si fuera obvio, no te habría confundido y desmentido. La regla fue hecha por nuestros desarrolladores senior, al igual que nuestra decisión de ejecutar StyleCop, FxCop y requerir pruebas unitarias y revisiones de código.
Andy
0

Creo que usar variables protegidas para las pruebas JUnit puede ser útil. Si los hace privados, no podrá acceder a ellos durante la prueba sin reflexión. Esto puede ser útil si está probando un proceso complejo a través de muchos cambios de estado.

Podría hacerlos privados, con métodos getter protegidos, pero si las variables solo se van a usar externamente durante una prueba JUnit, no estoy seguro de que sea una mejor solución.

Mella
fuente
-1

Sí, estoy de acuerdo en que es algo extraño dado que (como recuerdo) el libro también analiza todas las variables no privadas como algo que debe evitarse.

Creo que el problema con el modificador protegido es que el miembro no solo está expuesto a las subclases, sino que también está visible para todo el paquete. Creo que es este aspecto dual del que tío Bob está haciendo una excepción aquí. Por supuesto, uno no siempre puede evitar tener métodos protegidos y, por lo tanto, la calificación de variable protegida.

Creo que un enfoque más interesante es hacer que todo sea público, eso te obligará a tener clases pequeñas, lo que a su vez hace que la métrica de distancia vertical sea algo discutible.

CortinaPerro
fuente