Acabo de leer el enlace del artículo que publicaste, tengo que decir que Fowler ha hecho algunos puntos muy buenos y muchas cosas que dijo, he estado abogando por nuestro equipo durante años.
En mi opinión, si haces un diseño decente, no deberías meterte en lo que se consideraría una situación sin salida. Siempre he visto el software como compuesto por bloques de construcción . Todavía creo en un diseño inicial, pero el objetivo principal no es diseñar el producto completo, sino proporcionar una arquitectura / dirección general para que su equipo pueda visualizar una imagen común en la que todos estamos trabajando. Si tienes un montón de piezas de cubo y triángulo, es útil esbozar cómo se armaría un castillo antes de simplemente comenzar a juntar piezas.
Como vengo de OO land, para mí cada bloque es una clase y la superficie de ese bloque es la interfaz pública (lo que es visible por clases externas o derivadas). Si sigue buenos principios de SOLID, se asegurará de que cada bloque sea extremadamente simple y tenga una interfaz pública intuitiva. Volviendo a mi analogía, desea asegurarse de que el código solo crea formas simples. Cada vez que crea clases, que son demasiado complejas (muchas funciones, muchas variables), crea formas que son difíciles de reutilizar cuando cambian los requisitos.
Estoy de acuerdo con Fowler en que el mayor riesgo / desafío para el diseño evolutivo es que dejes las decisiones de diseño al tiempo de codificación, y esperas que cada desarrollador individual tome esas decisiones. Aquí es donde el sistema puede fallar si no tiene mecanismos de retroalimentación adecuados. Cada vez que se solicita una nueva característica, es extremadamente tentador simplemente encontrar la función que necesita ser extendida, poner algún tipo de condicional dentro de ella y simplemente agregar un montón de código dentro de esa función. Y a veces, esto podría ser todo lo que se necesita, pero esta también es (IMO) la práctica más común que conduce a componentes sin salida. Esto no tiene nada que ver con el diseño evolutivo. Esto es lo que se llama "sin diseño".
Siempre que se tome el tiempo de dar un paso atrás y decir, espere un minuto, esta clase ya tiene 15 variables miembro, permítame extraer 6 de estas y ponerlas en su propia clase autónoma, su software estará compuesto de muy poca luz. Ligero, flexible y reutilizable. Claro, si aparecen los PM y cambian la mitad de los requisitos de productos para usted, es posible que tenga que sacar algunos de sus bloques, volver a colocarlos en el estante y elaborar algunos nuevos (al igual que cuando construye un castillo, es posible que no use todos tus cilindros) Pero en ese punto, eso es solo parte de hacer negocios. Los requisitos cambiaron y al mantener su código flexible y modular, debería poder cambiar su producto para alinearse con su nueva dirección comercial.
Creo que este enfoque evolutivo del diseño funciona con todos los niveles de habilidad del ingeniero. Personalmente, he desarrollado software durante mucho tiempo y antes de que nuestro equipo adoptara una metodología ágil, fui responsable de enviar varios componentes principales de mi PC de desarrollo casi directamente al cliente sin apenas QA. Al mismo tiempo, esos componentes siempre han permanecido flexibles y mantenibles.
Solo estoy tratando de decir que me consideraría relativamente decente en el diseño de software. Al mismo tiempo, si me pidiera que redactara un documento de diseño de 100 páginas, se lo entregara a un codificador y esperara que funcionara, probablemente no podría diseñarme con una bolsa de papel. Al comenzar a trabajar, a veces dibujaba unos pocos diagramas similares a UML (muy simplificados, no en lenguaje completo), pero a medida que comienzo a codificar, refactorizaría según fuera necesario y mi código final nunca se vería como lo dibujé originalmente. Incluso si me paso un mes o dos pensando en cada pequeño detalle, no puedo imaginar que otra persona pueda tomar mis diagramas y crear un software sólido sin modificar el diseño mientras están codificando.
En el otro extremo del espectro, actualmente en mi equipo (ahora ágil y lo apoyo totalmente) tenemos un par de tipos que se unieron a nosotros desde tierras incrustadas donde solo han hecho C durante los últimos 15 años. Obviamente, ayudé con algunas clases iniciales de planificación y diseño, pero también me aseguré de seguir con revisiones periódicas de código y sesiones de lluvia de ideas en las que discutimos aplicaciones de SOLID y principios de diseño. Produjeron un código de espagueti que me hizo encogerme un poco, pero con solo un ligero empujón de mi parte, comenzaron a refactorizar lo que ya se había producido y la parte divertida es que uno de ellos regresó a mí unos días después y dice: Odio para decirlo, pero después de mover ese código, esto parece mucho más legible y comprensible. Callejón sin salida evitado. Punto I ' Lo que intento hacer es que incluso alguien que sea completamente nuevo en OO pueda producir un código algo decente, siempre que tenga un mentor con más experiencia, para recordarle que "diseño evolutivo" no es lo mismo que "no diseño". E incluso algunas de sus clases "más complejas" no dan tanto miedo porque cada clase no tiene tanta responsabilidad (es decir, no tiene tanto código), por lo que lo peor es peor, si esa clase "termina sin salida", nosotros tíralo y escribe una clase de reemplazo que tenga la misma interfaz pública (hasta ahora nunca vi la necesidad de esta contingencia en nada de lo que escribimos y he estado haciendo revisiones de código dos veces por semana).
Como nota final, también creo firmemente en los documentos de diseño (al menos para las condiciones comerciales de mi equipo actual), pero el objetivo principal de nuestros documentos de diseño es la Memoria Organizacional , por lo que los documentos reales se escriben después de que se produce el código y refactorizado. Antes de la codificación, generalmente tenemos una fase de diseño rápida (a veces no tan rápida) en la que esbozamos clases sobre servilletas / mspaint / visio y siempre les recuerdo a las personas que esta fase produce un camino a seguir, no un plan y cuando comienzan a codificar, todo lo que no tiene sentido debe cambiarse. Incluso con estos recordatorios, los tipos más nuevos tienden a intentar volver a encajar el código en el diseño original, sin importar cuán antinatural se sienta incluso para ellos. Esto generalmente aparece en las revisiones de código.
Dang, escribí mucho. Lo siento por eso.
Yo diría que el fenómeno del "diseño sin salida" es ortogonal a los métodos ágiles. Lo que quiero decir es que es posible hacer una cascada, pasar mucho tiempo por adelantado en un diseño (malo). Luego, dedique mucho tiempo a implementarlo solo para encontrarse en un callejón sin salida.
En todo caso, los métodos ágiles deberían ayudarlo a descubrir antes que tomó malas decisiones de diseño. La razón de esto es que su cartera de pedidos debe tener primero los elementos de mayor valor para el cliente y debe concentrarse en entregar incrementos útiles del software. Si su diseño le permite ofrecer un alto valor y utilidad, ya es bueno para algo :-) En contraste, podría tener un mal diseño en una situación de cascada donde es posible que no descubra durante muchos años que este diseño no puede ofrecer cualquier valor y utilidad: todo lo que tiene es la ilusión de que es un buen diseño. Como se suele decir, la prueba está en el pudín.
La otra cara es que, incluso en métodos ágiles, es importante tener una visión viable para el diseño del sistema que impulse las decisiones de iteración en iteración. Creo que Ken Schwabber dijo algo así como que si tienes un equipo de desarrolladores terribles, producirán mal software constantemente iteración por iteración. Ágil simplemente significa que no debe pasar mucho tiempo por adelantado porque tiene limitaciones en lo que puede aprender o imaginar antes de comenzar a implementar ( y los requisitos también cambian). Sin embargo, hay algunas situaciones en las que tiene que hacer un trabajo inicial (por ejemplo, investigación) y luego debe hacerlo.
¿Cómo evitas los callejones sin salida?
Diría principalmente anticipando requisitos futuros. Esto es algo que obtienes con experiencia y familiaridad con proyectos / productos similares. Esta anticipación es en parte lo que le ayuda a establecer un buen diseño porque se hace muchas preguntas de "qué pasaría si" sobre su sistema actual. Para mí este es el componente crítico. Técnicas como OO simplemente te están ayudando cuando ya sabes lo que estás haciendo.
¿Qué haces si tienes un callejón sin salida?
Un "callejón sin salida" no es diferente a cualquier otro bloque técnico que se encuentre durante el desarrollo de algo nuevo. Lo primero que debe darse cuenta es que realmente no hay verdaderos "callejones sin salida" que lo obliguen a retroceder por completo. Por lo menos, su aprendizaje hasta este punto es lo que le permite avanzar para que el esfuerzo no se desperdicie. Cuando llegas a un callejón sin salida, tienes un problema . El problema es lo que debe cambiar para cumplir con algún requisito nuevo (o antiguo) y cómo optimizar la realización de este cambio. Todo lo que tienes que hacer ahora es resolver este problema. Agradezca que esto es software y no, por ejemplo, un diseño de avión, porque el cambio es mucho más fácil. Identifique los problemas, corríjalos == refactor == ingeniería de software. A veces hay mucho trabajo involucrado ...
Si usa Scrum, este cambio debería ser impulsado naturalmente por las historias de los usuarios (¿qué obtiene el usuario de este cambio?). El proceso comenzaría desde una historia que no puede ser fácilmente acomodada por el diseño actual (oops) y se produciría una discusión con el propietario del producto sobre cómo desglosar esta historia. Sigue aplicando principios ágiles a través de este cambio.
Me vienen a la mente algunos cambios de grandes requisitos famosos del mundo del SO:
De cualquier manera que mires estos, son mucho trabajo. Es casi seguro que el diseño original no tuvo en cuenta la posibilidad de que esto ocurriera (es decir, la portabilidad no era un gran requisito). Si el diseño fue OO o no, probablemente tampoco sea un gran factor. En un buen diseño, las porciones específicas de la plataforma estarían algo aisladas y el trabajo sería más fácil.
fuente
Refactorizo mis proyectos permanentemente y también uso diagramas de clase UML. Quiero decir que creo uno o más diagramas de clases por paquetes. Cada diagrama se guarda en la raíz del paquete. Cada clasificador UML tiene un ID propio que se asigna al ID Java relacionado. Significa que cuando abro mi diagrama, se actualiza automáticamente a los últimos cambios de refactorización de código. También puedo cambiar directamente mis diagramas de clase a nivel gráfico y todo mi proyecto se refactoriza de inmediato. Funciona bastante bien, pero nunca reemplazará a los humanos. Mi diagrama de clase UML también es solo una vista gráfica de mi código. Es muy importante no mezclar el código y el modelo como lo está haciendo EMF eclipse porque tan pronto como se realiza la refactorización, también se pierde la información del modelo. Nunca uso el generador de código de Model Driven Development porque esto es inútil. Yo no'
Habiendo dicho eso, tener más de 100 diagramas de clases que representan todos los detalles de la estructura de mi proyecto y están llenos de notas en todas partes es realmente útil. Solo creo diagramas de clase para proyectos porque generalmente los desarrolladores no tienen tiempo para aprender o usar otros diagramas. Los diagramas de clases también son muy buenos porque se actualizan automáticamente. Los diagramas de clase se pueden crear después del código simplemente invirtiendo un paquete y agregando notas. Es rápido y siempre preciso y 100% iterativo.
No confunda el desarrollo impulsado por modelos, que es un código generador de modelos y, por lo general, utiliza UML como presentación gráfica con diagramas de clase UML actualizados desde el código. Solo el código sincronizado UML tiene un valor real para mí si hay varias iteraciones.
Lamento ser tan largo, pero creo que deberíamos dar una segunda oportunidad a los diagramas de clase UML si solo se usa como una vista gráfica de nuestro proyecto. Significa que UML cubre el proyecto completo y tiene un solo modelo compuesto por diagramas de clase grandes que representan el proyecto completo. Sería ridículo tener cientos de vistas pequeñas y un modelo para cada vista dentro de un proyecto que tenga cientos de vistas :-)
fuente
He llegado a un punto muerto en mi código y en otros debido a un mal diseño, cambio de dirección, etc. También he visto a muchos otros encontrarse con este problema. El gran error (al menos me parece un error) es el deseo inmediato de desechar el código de trabajo y volver a implementar todo desde cero.
Abordé cada caso de la misma manera que parecía funcionar bien:
Costos:
Beneficios:
fuente
Hace aproximadamente un mes o dos, nuestro proyecto actual se atascó un poco debido a algunas malas decisiones de diseño (y la falta de mucho diseño en un solo lugar), con el estilo de desarrollo SCRUM.
Nuestra solución (y creo que es la estándar para SCRUM) fue dedicar un sprint completo (~ 2 semanas) a nada más que refactorizar. No se agregó ninguna funcionalidad nueva durante este tiempo, pero pudimos pensar en la base de código actual y diseñar un sistema mucho mejor para lo que estábamos haciendo.
Ya hemos superado ese obstáculo y hemos agregado nuevas características nuevamente.
fuente
La clave para limitar el costo de los cambios de diseño es mantener el código lo más SECO posible. Esto conducirá la mayor parte del código de la aplicación a un nivel muy alto, donde la mayor parte del código expresa directamente la intención y especifica relativamente poco el mecanismo. Si hace esto, las decisiones de diseño tendrán la expresión más pequeña posible en el código, y los cambios de diseño tendrán el menor costo posible.
fuente
La clave para evitar callejones sin salida del diseño es reconocer lo antes posible cuando su diseño necesita cambiar, y cambiarlo entonces. Los mayores problemas no surgen al evolucionar continuamente su diseño, sino al negarse a evolucionar su diseño hasta que sea un gran problema.
Como ejemplo, Netflix tiene una función de perfil, donde diferentes miembros de la familia pueden facturar al mismo plan, pero tienen colas separadas. Hace unos años, anunciaron que tendrían que cancelar esa función, porque solo alrededor del 10% de sus usuarios la usaron, pero debido a la implementación pirateada, estaba consumiendo una cantidad excesiva de trabajo de mantenimiento. Después de un alboroto, mordieron la bala e hicieron un rediseño costoso para mantener a esos clientes.
Estoy seguro de que hubo algunos ingenieros que reconocieron un diseño subóptimo cuando agregaron esa característica por primera vez. Si lo hubieran cambiado en ese entonces, no habría sido tan importante.
fuente
¿No fue Fred Brooks quien dijo algo como "Planea tirar el primero"? No se sienta demasiado triste al respecto, los diseños sin salida también aparecen en proyectos que también intentan hacer todo el diseño por adelantado. Los rediseños ocurren en todo tipo de desarrollo, ya sea porque fue un diseño inviable desde el principio (ese último 20% que se pasa por alto "el diablo está en los detalles") o porque un cliente cambia su enfoque. No hay necesidad real de alarmas, no se preocupe demasiado.
fuente