Estoy trabajando en un equipo donde el líder del equipo es un defensor virulento de los principios de desarrollo SOLID. Sin embargo, carece de mucha experiencia en sacar el software complejo de la puerta.
Tenemos una situación en la que ha aplicado SRP a lo que ya era una base de código bastante compleja, que ahora se ha vuelto muy fragmentada y difícil de entender y depurar.
Ahora tenemos un problema no solo con la fragmentación del código, sino también con la encapsulación, ya que los métodos dentro de una clase que pueden haber sido privados o protegidos han sido considerados como una 'razón para cambiar' y han sido extraídos a clases e interfaces públicas o internas que no está de acuerdo con los objetivos de encapsulación de la aplicación.
Tenemos algunos constructores de clase que toman más de 20 parámetros de interfaz, por lo que nuestro registro y resolución de IoC se está convirtiendo en un monstruo por derecho propio.
Quiero saber si existe algún enfoque de "refactorización fuera de SRP" que podamos utilizar para ayudar a solucionar algunos de estos problemas. He leído que no viola SOLID si creo un número de clases vacías de grano grueso que 'envuelven' un número de clases estrechamente relacionadas para proporcionar un punto de acceso único a la suma de su funcionalidad (es decir, imitando un menor implementación de clase demasiado SRP'd).
Aparte de eso, no puedo pensar en una solución que nos permita continuar pragmáticamente con nuestros esfuerzos de desarrollo, mientras mantenemos contentos a todos.
Alguna sugerencia ?
fuente
ISomething
). En mi humilde opinión, estos enfoques son mucho más fáciles de manejar que la inyección de dependencia y dan como resultado un código más legible.Respuestas:
Si su clase tiene 20 parámetros en el constructor, no parece que su equipo sepa lo que es SRP. Si tiene una clase que solo hace una cosa, ¿cómo tiene 20 dependencias? Es como ir a un viaje de pesca y traer una caña de pescar, una caja de aparejos, suministros para acolchar, bola de boliche, nunchucks, lanzallamas, etc. Si necesita todo eso para ir a pescar, no solo va a pescar.
Dicho esto, SRP, como la mayoría de los principios, puede aplicarse en exceso. Si crea una nueva clase para incrementar enteros, entonces sí, esa puede ser una responsabilidad única, pero vamos. Eso es ridículo. Tendemos a olvidar que cosas como los principios SOLID están ahí para un propósito. SOLID es un medio para un fin, no un fin en sí mismo. El fin es la mantenibilidad . Si va a obtener esa granularidad con el Principio de responsabilidad única, es un indicador de que el celo por SOLID ha cegado al equipo a la meta de SOLID.
Entonces, supongo que lo que digo es ... El SRP no es tu problema. Es un malentendido del SRP o una aplicación increíblemente granular del mismo. Intenta que tu equipo mantenga lo principal como lo principal. Y lo principal es la mantenibilidad.
EDITAR
Haga que las personas diseñen módulos de una manera que fomente la facilidad de uso. Piense en cada clase como una mini API. Piense primero, "¿Cómo me gustaría usar esta clase" y luego impleméntela. No solo piense "¿Qué necesita hacer esta clase?" El SRP tiene una gran tendencia a hacer que las clases sean más difíciles de usar, si no piensa mucho en la usabilidad.
EDITAR 2
Si está buscando consejos sobre refactorización, puede comenzar a hacer lo que sugirió: crear clases más generales para envolver a otras. Asegúrese de que la clase de grano más grueso todavía se adhiera al SRP , pero en un nivel superior. Entonces tienes dos alternativas:
Cuando termine de refactorizar (pero antes de comprometerse con el repositorio), revise su trabajo y pregúntese si su refactorización fue realmente una mejora en la facilidad de mantenimiento y facilidad de uso.
fuente
Customer
clase y tener un código más fácil de mantener. Ver ejemplos aquí: codemonkeyism.com/…Creo que es en la Refactorización de Martin Fowler que una vez leí una contra-regla a SRP, definiendo a dónde va demasiado lejos. Hay una segunda pregunta, tan importante como "¿tiene cada clase una sola razón para cambiar?" y eso es "¿cada cambio solo afecta a una clase?"
Si la respuesta a la primera pregunta es, en todos los casos, "sí", pero la segunda pregunta es "ni siquiera cerrada", entonces debe analizar nuevamente cómo está implementando SRP.
Por ejemplo, si agregar un campo a una tabla significa que tiene que cambiar un DTO y una clase de validador y una clase de persistencia y un objeto de modelo de vista, etc., entonces ha creado un problema. Tal vez deberías repensar cómo has implementado SRP.
Quizás haya dicho que agregar un campo es la razón para cambiar el objeto Cliente, pero cambiar la capa de persistencia (por ejemplo, de un archivo XML a una base de datos) es otra razón para cambiar el objeto Cliente. Entonces decide crear un objeto CustomerPersistence también. Pero si lo hace de tal manera que agregar un campo TODAVÍA requiere un cambio en el objeto CustomerPersisitence, ¿cuál fue el punto? Todavía tiene un objeto con dos razones para cambiar: ya no es Cliente.
Sin embargo, si introduce un ORM, es muy posible que pueda hacer que las clases funcionen de manera que si agrega un campo al DTO, cambiará automáticamente el SQL utilizado para leer esos datos. Entonces tienes una buena razón para separar las dos preocupaciones.
En resumen, esto es lo que tiendo a hacer: si hay un balance aproximado entre la cantidad de veces que digo "no, hay más de una razón para cambiar este objeto" y la cantidad de veces que digo "no, este cambio será afectar a más de un objeto ", entonces creo que tengo el equilibrio correcto entre SRP y la fragmentación. Pero si ambos siguen altos, empiezo a preguntarme si hay una manera diferente de separar las preocupaciones.
fuente
El hecho de que un sistema sea complejo no significa que deba complicarlo . Si tiene una clase que tiene demasiadas dependencias (o Colaboradores) como esta:
... entonces se volvió demasiado complicado y realmente no estás siguiendo SRP , ¿verdad? Apuesto a que si anota lo que
MyAwesomeClass
hace en una tarjeta CRC , no cabría en una tarjeta índice o si tiene que escribir en letras minúsculas ilegibles.Lo que tienes aquí es que tus muchachos solo siguieron el Principio de segregación de interfaz y pueden haberlo llevado al extremo, pero esa es otra historia. Se podría argumentar que las dependencias son objetos de dominio (lo que sucede), sin embargo, tener una clase que maneje 20 objetos de dominio al mismo tiempo es demasiado extenso.
TDD le proporcionará un buen indicador de cuánto hace una clase. Sin rodeos puesto; Si un método de prueba tiene un código de configuración que tarda una eternidad en escribir (incluso si refactoriza las pruebas), entonces
MyAwesomeClass
probablemente tenga demasiadas cosas que hacer.Entonces, ¿cómo se resuelve este enigma? Mueves las responsabilidades a otras clases. Hay algunos pasos que puede seguir en una clase que tiene este problema:
Un ejemplo abstracto sobre refactorizar responsabilidades
Vamos a
C
ser una clase que tiene varias dependenciasD1
,D2
,D3
,D4
que es necesario perfeccionar por usar menos. Cuando identificamos los métodos queC
requieren las dependencias, podemos hacer una lista simple:D1
-performA(D2)
,performB()
D2
-performD(D1)
D3
-performE()
D4
-performF(D3)
Mirando la lista podemos ver eso
D1
yD2
estamos relacionados entre sí ya que la clase los necesita juntos de alguna manera. También podemos ver que lasD4
necesidadesD3
. Entonces tenemos dos agrupaciones:Group 1
-D1
<->D2
Group 2
-D4
->D3
Las agrupaciones son un indicador de que la clase ahora tiene dos responsabilidades.
Group 1
- Uno para manejar la llamada de dos objetos que se necesitan mutuamente. Tal vez pueda dejar que su claseC
elimine la necesidad de manejar ambas dependencias y dejar que una de ellas maneje esas llamadas. En esta agrupación, es obvio queD1
podría tener una referenciaD2
.Group 2
- La otra responsabilidad necesita un objeto para llamar a otro. No se puedeD4
manejar enD3
lugar de su clase? Entonces, probablemente podemos eliminarD3
de la claseC
dejando en su lugarD4
hacer las llamadas.No tome mi respuesta tal como está escrita, ya que el ejemplo es muy abstracto y hace muchas suposiciones. Estoy bastante seguro de que hay más formas de refactorizar esto, pero al menos los pasos pueden ayudarlo a obtener algún tipo de proceso para mover las responsabilidades en lugar de dividir las clases.
Editar:
Entre los comentarios @Emmad Karem dice:
Es cierto que los objetos DAO tienden a tener muchos parámetros, que debe establecer en su constructor, y los parámetros generalmente son tipos simples como cadena. Sin embargo, en el ejemplo de una
Customer
clase, aún puede agrupar sus propiedades dentro de otras clases para simplificar las cosas. Como tener unaAddress
clase con calles y unaZipcode
clase que contenga el código postal y que también maneje la lógica comercial, como la validación de datos:Esto se trata más adelante en la publicación del blog "Nunca, nunca, nunca use String en Java (o al menos a menudo)" . Como alternativa al uso de constructores o métodos estáticos para hacer que los subobjetos sean más fáciles de crear, puede usar un patrón de generador de fluidos .
fuente
Estoy de acuerdo con todas las respuestas sobre SRP y cómo se puede llevar demasiado lejos. En su publicación, menciona que debido a una "refactorización excesiva" para adherirse a SRP, encontró que la encapsulación se rompió o se modificó. Lo único que me ha funcionado es mantener siempre lo básico y hacer exactamente lo que se requiere para alcanzar un fin.
Cuando se trabaja con sistemas Legacy, el "entusiasmo" por arreglar todo para mejorarlo suele ser bastante alto en Team Leads, especialmente aquellos que son nuevos en ese rol. SOLID, simplemente no tiene SRP: esa es solo la S. Asegúrese de que si está siguiendo SOLID, no se olvide también de OLID.
Estoy trabajando en un sistema Legacy en este momento y comenzamos a seguir un camino similar al principio. Lo que funcionó para nosotros fue una decisión colectiva del equipo para sacar lo mejor de ambos mundos: SOLID y KISS (Keep It Simple Stupid). Discutimos colectivamente cambios importantes en la estructura del código y aplicamos el sentido común al aplicar varios principios de desarrollo. Son excelentes como directrices, no como "Leyes de desarrollo S / W". El equipo no se trata solo del Team Lead, se trata de todos los desarrolladores del equipo. Lo que siempre ha funcionado para mí es hacer que todos estén en una habitación y elaborar un conjunto de pautas compartidas que todo su equipo acuerde seguir.
Con respecto a cómo solucionar su situación actual, si usa un VCS y no ha agregado demasiadas características nuevas a su aplicación, siempre puede volver a una versión de código que todo el equipo considere comprensible, legible y fácil de mantener. ¡Si! Te pido que deseches el trabajo y comiences desde cero. Esto es mejor que tratar de "arreglar" algo que estaba roto y moverlo de nuevo a algo que ya existía.
fuente
La respuesta es la facilidad de mantenimiento y la claridad del código por encima de todo. Para mí eso significa escribir menos código , no más. Menos abstracciones, menos interfaces, menos opciones, menos parámetros.
Cada vez que evalúo una reestructuración de código o agrego una nueva característica, pienso en la cantidad de repeticiones necesarias en comparación con la lógica real. Si la respuesta es más del 50%, probablemente significa que ya no lo pienso.
Además de SRP, hay muchos otros estilos de desarrollo. En su caso, parece que definitivamente falta YAGNI.
fuente
Muchas de las respuestas aquí son realmente buenas, pero se centran en el aspecto técnico de este problema. Simplemente agregaré que parece que los intentos del desarrollador de seguir el sonido SRP como si realmente violaran el SRP.
Puede ver el blog de Bob aquí sobre esta situación, pero argumenta que si una responsabilidad se difunde en varias clases, entonces se viola la responsabilidad SRP porque esas clases cambian en paralelo. Sospecho que a tu desarrollador realmente le gustaría el diseño en la parte superior del blog de Bob, y podría estar un poco decepcionado de verlo destrozado. En particular porque viola el "Principio de cierre común": las cosas que cambian juntas permanecen juntas.
Recuerde que el SRP se refiere a la "razón del cambio" y no a "hacer una cosa", y que no necesita preocuparse por esa razón hasta que realmente ocurra un cambio. El segundo tipo paga por la abstracción.
Ahora está el segundo problema: el "defensor virulento del desarrollo SÓLIDO". Seguro que no parece que tengas una gran relación con este desarrollador, por lo que cualquier intento de convencerlo de los problemas en la base de código no funciona. Deberá reparar la relación para poder tener una discusión real de los problemas. Lo que recomendaría es cerveza.
No en serio, si no bebes dirígete a una cafetería. Sal de la oficina y relájate en un lugar donde puedas hablar de estas cosas de manera informal. En lugar de tratar de ganar una discusión en una reunión, que no lo hará, tener una discusión en algún lugar divertido. Trate de reconocer que este desarrollador, que lo está volviendo loco, es un humano que funciona y está tratando de sacar el software "por la puerta" y no quiere enviar basura. Como es probable que comparta ese punto en común, puede comenzar a discutir cómo mejorar el diseño sin dejar de ajustarse al SRP.
Si ambos pueden reconocer que el SRP es algo bueno, que simplemente interpretan los aspectos de manera diferente, probablemente puedan comenzar a tener conversaciones productivas.
fuente
Estoy de acuerdo con su decisión de liderar el equipo [actualización = 2012.05.31] de que SRP es generalmente una buena idea. Pero estoy totalmente de acuerdo con el comentario de @ Spoike de que un constructor con 20 argumentos de interfaz es demasiado. [/ Update]:
La introducción de SRP con IoC mueve la complejidad de una "clase multi-responsable" a muchas clases srp y una inicialización mucho más complicada para el beneficio de
Me temo que no puede reducir la fragmentación del código sin sacrificar srp.
Pero puede "aliviar el dolor" de la codeinicialización implementando una clase de azúcar sintáctica que oculta la complejidad de la inicialización en un constructor.
fuente