¿Qué haces si alcanzas un punto muerto de diseño en métodos evolutivos como Agile o XP?

11

Mientras leía la famosa publicación de blog de Martin Fowler Is Design Dead? , una de las impresionantes impresiones que obtuve es que, dado el hecho de que en Metodología ágil y programación extrema, el diseño y la programación son evolutivos, siempre hay puntos en los que las cosas deben ser refactorizadas.

Es posible que cuando el nivel de un programador sea bueno y comprenda las implicaciones del diseño y no cometa errores críticos, el código continúa evolucionando. Sin embargo, en un contexto normal, ¿cuál es la realidad básica en este contexto?

En un día normal, dado que se produce un desarrollo significativo en el producto, y cuando se produce un cambio crítico en los requisitos, ¿no es una restricción que cuánto deseamos, los aspectos fundamentales del diseño no pueden modificarse? (sin tirar la mayor parte del código). ¿No es muy probable que uno llegue a un punto muerto en cualquier posible mejora adicional en el diseño y los requisitos?

No estoy abogando por ninguna práctica no ágil aquí, pero quiero saber de las personas que practican métodos de desarrollo ágiles o iterativos o evolutivos, en cuanto a sus experiencias reales.

¿Alguna vez has llegado a tales callejones sin salida ? ¿Cómo has logrado evitarlo o escapar? ¿O hay medidas para garantizar que el diseño permanezca limpio y flexible a medida que evoluciona?

Dipan Mehta
fuente

Respuestas:

17

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.

DXM
fuente
1
+1 Valió la pena por cada palabra. Me encuentro con estas situaciones como las describiste y, una vez que se cumple el plazo, les pido que limpien (lean el refactorizador) más de lo que creo que es un sentido común del diseño. Pero a menudo la gente encuentra repetida la misma pregunta: ¿por qué estoy haciendo lo mismo otra vez? Supongo que ahora tengo la respuesta: si necesita un tiempo de comercialización más rápido y permite que el diseño evolucione, la refactorización no es una compensación por sus viejos pecados, sino que es algo legítimo.
Dipan Mehta
Sí, escribiste mucho, pero fue algo bueno. Realmente disfruté leerlo :).
Radu Murzea
11

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.

Guy Sirton
fuente
En realidad, las primeras versiones de Windows NT implementaron una "Capa de extracción de hardware" y admitían DEC Apha y x86. Como nunca nadie compró máquinas basadas en DEC alpha, esto se dejó en silencio. Me imagino que esta "independencia de la máquina" todavía existe en un formato vestigial en la versión actual, por lo que un puerto ARM puede no ser tan difícil.
James Anderson el
3

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 :-)

UML_GURU
fuente
1
¡+1 para mostrar la idea del código postal UML! Curiosamente, ¡nunca volvemos a los diagramas de documentos después del código!
Dipan Mehta
Sí, este es exactamente el problema con el desarrollo basado en modelos asociado a UML. Usted genera documentación y luego nunca la usa si alguna modificación del proyecto. La combinación de modelo y código nos permite usar UML y cambiarlo tantas veces como sea necesario.
UML_GURU
3

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:

  • identificar por qué el diseño actual no funciona
  • idear un nuevo diseño y un plan de transición
  • etiquetar el código que no se usará como obsoleto
  • implementar solo lo que necesito para el nuevo diseño para satisfacer la necesidad inmediata
  • eliminar el código que el nuevo código hace obsoleto

Costos:

  • mayor complejidad debido a 2 implementaciones en la base del código al mismo tiempo
  • más costo total por función / corrección de errores que hacer un rediseño / reimplementación completa suponiendo buenos casos de uso y pruebas

Beneficios:

  • al menos un orden de magnitud menos riesgo porque todavía tiene el antiguo diseño / código de trabajo
  • comercialización más rápida para cualquier característica 1 a n-1 que no requiera un rediseño completo
  • Si el producto / código termina muriendo antes de haber completado el rediseño completo, habrá ahorrado la diferencia de tiempo de desarrollo
  • enfoque iterativo y todos los beneficios en él
dietbuddha
fuente
2

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.

Izkata
fuente
Esta es otra gran fricción para encontrar. ¡Tener que decirle al cliente, que ahora estamos construyendo lo mismo, de características que no son nada nuevo, después de que se entregue la primera versión! ¿Con qué frecuencia y con frecuencia (si alguna vez) puede darse el lujo de decirle esto al cliente? o como explicas?
Dipan Mehta
Tiene dos opciones básicas, ya sea primero, simplemente dice la verdad simple: aunque lo que tiene funciona es un desastre y necesita ser arreglado para avanzar o segundo que dice que está construyendo infraestructura para apoyar el desarrollo continuo ( lo cual no es menos cierto, pero se hace girar para ser un poco más positivo). Puede concluir ambas cosas diciendo que para ofrecer la funcionalidad incurrimos en una deuda técnica que ahora debe pagarse.
Murph el
@Dipan Mehta: Bueno, supongamos que un cliente quiere una casa de dos pisos. Lo diseñas y lo entregas. Luego quieren tener cuatro pisos adicionales. Usted dice, bueno, tengo que pasar este tiempo solo para hacer que el edificio actual sea más robusto para que pueda albergar cuatro pisos adicionales. Por lo tanto, no creo que esto deba ser un problema para el cliente si el plan original solo incluye dos pisos. Si se planificaron seis pisos desde el principio, sí, podría ser un problema informarle al cliente.
Giorgio
@DipanMehta También somos un poco afortunados porque los clientes no necesariamente conocen este proyecto. Es una actualización de un producto que usan actualmente, con una fecha de finalización semi vaga de alrededor de finales de este año. Por lo tanto, ni siquiera necesitan saber acerca de un retraso para la refactorización;) (El gerente que maneja la mayoría de las decisiones de diseño es interno)
Izkata
2

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.

Kevin Cline
fuente
2

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.

Karl Bielefeldt
fuente
1

¿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.

luego
fuente