Entonces, probablemente, como muchos, a menudo me encuentro con dolores de cabeza con problemas de diseño en los que, por ejemplo, hay algún patrón / enfoque de diseño que parece ajustarse intuitivamente al problema y tiene los beneficios deseados. Muy a menudo hay algunas advertencias que dificultan la implementación del patrón / enfoque sin algún tipo de trabajo en torno al cual luego se niega el beneficio del patrón / enfoque. Puedo terminar fácilmente iterando a través de muchos patrones / enfoques porque, como era de esperar, casi todos tienen algunas advertencias muy significativas en situaciones del mundo real en las que simplemente no hay una solución fácil.
Ejemplo:
Te daré un ejemplo hipotético basado libremente en uno real que encontré recientemente. Digamos que quiero usar la composición sobre la herencia porque las jerarquías de herencia han obstaculizado la escalabilidad del código en el pasado. Podría refactorizar el código pero luego encontrar que hay algunos contextos en los que la superclase / clase base simplemente necesita llamar a la funcionalidad en la subclase, a pesar de los intentos de evitarla.
El siguiente mejor enfoque parece ser la implementación de un patrón de medio delegado / observador y medio patrón de composición para que la superclase pueda delegar el comportamiento o para que la subclase pueda observar eventos de superclase. Entonces, la clase es menos escalable y mantenible porque no está claro cómo se debe extender, también es difícil extender los oyentes / delegados existentes. Además, la información no se oculta bien porque uno comienza a necesitar conocer la implementación para ver cómo extender la superclase (a menos que use comentarios muy extensamente).
Entonces, después de esto, podría optar por simplemente usar observadores o delegados por completo para alejarnos de los inconvenientes que conlleva mezclar los enfoques en gran medida. Sin embargo, esto viene con sus propios problemas. Por ejemplo, podría encontrar que termino necesitando observadores o delegados para una cantidad cada vez mayor de comportamientos hasta que termino necesitando observadores / delegados para prácticamente cada comportamiento. Una opción podría ser tener un gran oyente / delegado para todo el comportamiento, pero luego la clase implementadora termina con muchos métodos vacíos, etc.
Entonces podría intentar otro enfoque, pero hay tantos problemas con eso. Luego el siguiente, y el siguiente, etc.
Este proceso iterativo se vuelve muy difícil cuando cada enfoque parece tener tantos problemas como cualquiera de los otros y conduce a una especie de parálisis de decisión de diseño . También es difícil aceptar que el código terminará siendo igualmente problemático independientemente de qué patrón de diseño o enfoque se use. Si termino en esta situación, ¿significa que el problema en sí necesita ser repensado? ¿Qué hacen los demás cuando se encuentran con esta situación?
Editar: Parece que hay una serie de interpretaciones de la pregunta que quiero aclarar:
- He eliminado la OOP de la pregunta por completo porque resulta que en realidad no es específica de la OOP, además es demasiado fácil interpretar mal algunos de los comentarios que hice al pasar sobre la OOP.
- Algunos han afirmado que debería adoptar un enfoque iterativo e intentar diferentes patrones, o que debería descartar un patrón cuando deje de funcionar. Este es el proceso al que me refería en primer lugar. Pensé que esto estaba claro en el ejemplo, pero podría haberlo dejado más claro, así que he editado la pregunta para hacerlo.
Respuestas:
Cuando llego a una decisión difícil como esa, generalmente me hago tres preguntas:
¿Cuáles son los pros y los contras de todas las soluciones disponibles?
¿Hay alguna solución que aún no haya considerado?
Lo más importante:
¿Cuáles son exactamente mis requisitos? ¿No son los requisitos en papel, los verdaderos fundamentales?
¿Puedo reformular de alguna manera el problema / ajustar sus requisitos, de modo que permita una solución simple y directa?
¿Puedo representar mis datos de alguna manera diferente, de modo que permita una solución tan simple y directa?
Estas son las preguntas sobre los fundamentos del problema percibido. Puede resultar que en realidad estabas tratando de resolver el problema incorrecto. Su problema puede tener requisitos específicos que permitan una solución mucho más simple que el caso general. ¡Cuestiona tu formulación de tu problema!
Me resulta muy importante pensar mucho en las tres preguntas antes de continuar. Salga a caminar, pasee por su oficina, haga lo que sea necesario para reflexionar sobre las respuestas a estas preguntas. Sin embargo, responder a estas preguntas no debería llevar mucho tiempo. Los tiempos adecuados pueden variar de 15 minutos a algo así como una semana, dependen de la maldad de las soluciones que ya ha encontrado y su impacto en general.
El valor de este enfoque es que a veces encontrarás soluciones sorprendentemente buenas. Soluciones elegantes Las soluciones bien valen el tiempo que invirtió en responder estas tres preguntas. Y no encontrará esa solución, si solo ingresa la siguiente iteración inmediatamente.
Por supuesto, a veces no parece existir una buena solución. En ese caso, está atrapado con sus respuestas a la pregunta uno, y lo bueno es simplemente lo menos malo. En este caso, el valor de tomarse el tiempo para responder estas preguntas es que posiblemente evite las iteraciones que están destinadas a fallar. Solo asegúrese de volver a la codificación dentro de un período de tiempo adecuado.
fuente
Lo primero es lo primero: los patrones son abstracciones útiles, no el final, todo es diseño, y mucho menos el diseño OO.
En segundo lugar, la OO moderna es lo suficientemente inteligente como para saber que no todo es un objeto. A veces, el uso de funciones antiguas simples, o incluso algunos scripts de estilo imperativos, dará una mejor solución para ciertos problemas.
Ahora a la carne de las cosas:
¿Por qué? ¡Cuando tienes un montón de opciones similares, tu decisión debería ser más fácil! No perderá mucho eligiendo la opción "incorrecta". Y realmente, el código no es fijo. Pruebe algo, vea si es bueno. Iterar .
Nueces duras. Problemas difíciles: los problemas matemáticos reales son simplemente difíciles. Probablemente difícil. Literalmente no hay una buena solución para ellos. Y resulta que los problemas fáciles no son realmente valiosos.
Pero ten cuidado. Con demasiada frecuencia, he visto a personas frustradas por no tener buenas opciones porque están estancados mirando el problema de cierta manera, o porque han reducido sus responsabilidades de una manera que no es natural para el problema en cuestión. "No es una buena opción" puede ser un olor que hay algo fundamentalmente malo con su enfoque.
Lo perfecto es enemigo de lo bueno. Haga que algo funcione, luego refactorícelo.
Como he mencionado, el desarrollo iterativo generalmente minimiza este problema. Una vez que algo funciona, está más familiarizado con el problema que lo coloca en una mejor posición para resolverlo. Tiene un código real para mirar y evaluar, no un diseño abstracto que no parece correcto.
fuente
La situación que está describiendo parece un enfoque de abajo hacia arriba. Toma una hoja, trata de arreglarla y descubre que está conectada a una rama que también está conectada a otra rama, etc.
Es como tratar de construir un automóvil comenzando con un neumático.
Lo que necesita es dar un paso atrás y mirar la imagen más grande. ¿Cómo se sienta esa hoja en el diseño general? ¿Sigue siendo actual y correcto?
¿Cómo se vería el módulo si lo diseñaras y lo implementaras desde cero? Cuán lejos de ese "ideal" está su implementación actual.
De esa manera, tiene una idea más amplia de hacia qué trabajar. (O si decide que es demasiado trabajo, cuáles son los problemas).
fuente
Su ejemplo describe una situación que surge típicamente con piezas más grandes de código heredado, y cuando intenta hacer una refactorización "demasiado grande".
El mejor consejo que puedo darle para esta situación es:
no intentes alcanzar tu objetivo principal en una "gran explosión" ,
¡aprende a mejorar tu código en pasos más pequeños!
Por supuesto, eso es más fácil de escribir que de hacer, entonces, ¿cómo lograr esto en realidad? Bueno, necesita práctica y experiencia, depende mucho del caso, y no existe una regla estricta que diga "haga esto o aquello" que se ajuste a cada caso. Pero déjame usar tu ejemplo hipotético. "composición sobre herencia" no es un objetivo de diseño de todo o nada, es un candidato perfecto para lograrlo en varios pasos pequeños.
Digamos que notó que "composición sobre herencia" es la herramienta adecuada para el caso. Debe haber algunas indicaciones para que este sea un objetivo sensato, de lo contrario no habría elegido esto. Así que supongamos que hay una gran cantidad de funcionalidades en la superclase que simplemente se "llama" desde las subclases, por lo que esta funcionalidad es candidata para no permanecer en esa superclase.
Si observa que no puede eliminar inmediatamente la superclase de las subclases, puede comenzar primero refactorizando la superclase en componentes más pequeños que encapsulan la funcionalidad mencionada anteriormente. Comience con las frutas más bajas, primero extraiga algunos componentes más simples, lo que ya hará que su superclase sea menos compleja. Cuanto más pequeña sea la superclase, más fáciles serán las refactorizaciones adicionales. Utilice estos componentes de las subclases, así como de la superclase.
Si tiene suerte, el código restante en la superclase será tan simple a lo largo de este proceso que puede eliminar la superclase de las subclases sin más problemas. O notará que mantener la superclase ya no es un problema, ya que ya extrajo suficiente código en los componentes que desea reutilizar sin herencia.
Si no está seguro de por dónde comenzar, porque no sabe si una refactorización resultará simple, a veces el mejor enfoque es hacer una refactorización por rasguño .
Por supuesto, su situación real podría ser más complicada. Así que aprende, reúne experiencia y sé paciente, hacer esto bien lleva años. Aquí puedo recomendar dos libros, tal vez los encuentre útiles:
Refactorización por Fowler: describe un catálogo completo de refactorizaciones muy pequeñas .
Trabajando eficazmente con el código heredado de Feathers: brinda excelentes consejos sobre cómo lidiar con grandes fragmentos de código mal diseñado y hacerlo más comprobable en pasos más pequeños
fuente
A veces, los dos mejores principios de diseño son KISS * y YAGNI **. No sienta la necesidad de incluir cada patrón de diseño conocido en un programa que solo tenga que imprimir "¡Hola, mundo!".
Edite después de la actualización de la pregunta (y refleje lo que Pieter B dice hasta cierto punto):
A veces tomas una decisión arquitectónica desde el principio que te lleva a un diseño particular que conduce a todo tipo de fealdad cuando intentas implementarlo. Desafortunadamente, en ese punto, la solución "adecuada" es dar un paso atrás y averiguar cómo llegaste a esa posición. Si aún no puede ver la respuesta, siga retrocediendo hasta que lo haga.
Pero si el trabajo para hacerlo sería desproporcionado, se necesita una decisión pragmática para encontrar la solución menos fea para el problema.
fuente
Cuando estoy en esta situación, lo primero que hago es parar. Me cambio a otro problema y trabajo en eso por un tiempo. Tal vez una hora, tal vez un día, tal vez más. Esa no siempre es una opción, pero mi subconsciente trabajará en cosas mientras mi cerebro consciente hace algo más productivo. Finalmente, vuelvo a hacerlo con ojos nuevos e intento nuevamente.
Otra cosa que hago es preguntarle a alguien más inteligente que yo. Esto puede tomar la forma de preguntar en Stack Exchange, leer un artículo en la web sobre el tema o preguntarle a un colega que tenga más experiencia en esta área. A menudo, el método que creo que es el enfoque correcto resulta ser completamente incorrecto para lo que intento hacer. He caracterizado mal algunos aspectos del problema y, en realidad, no se ajusta al patrón que creo que sí. Cuando eso sucede, que alguien más diga: "Sabes, esto se parece más a ..." puede ser de gran ayuda.
Relacionado con lo anterior está el diseño o la depuración por confesión. Vas a un colega y le dices: "Te diré el problema que tengo y luego te explicaré las soluciones que tengo. Señalas los problemas en cada enfoque y sugieres otros enfoques. ". A menudo, incluso antes de que la otra persona hable, como estoy explicando, empiezo a darme cuenta de que un camino que parecía igual a los demás es en realidad mucho mejor o peor de lo que pensaba originalmente. La conversación resultante puede reforzar esa percepción o señalar cosas nuevas en las que no pensé.
Entonces TL; DR: Tómate un descanso, no lo fuerces, pide ayuda.
fuente