Un amigo mío está trabajando para una pequeña empresa en un proyecto que todo desarrollador odiaría: está presionado para lanzar lo más rápido posible, es el único que parece preocuparse por la deuda técnica, el cliente no tiene antecedentes técnicos, etc.
Me contó una historia que me hizo pensar en la idoneidad de los patrones de diseño en proyectos como este. Aquí está la historia.
Tuvimos que mostrar productos en diferentes lugares del sitio web. Por ejemplo, los administradores de contenido podrían ver los productos, pero también los usuarios finales o los socios a través de la API.
A veces, faltaba información de los productos: por ejemplo, un montón de ellos no tenía ningún precio cuando se creó el producto, pero el precio aún no se había especificado. Algunos no tenían una descripción (la descripción es un objeto complejo con historiales de modificación, contenido localizado, etc.). Algunos carecían de información de envío.
Inspirado por mis lecturas recientes sobre patrones de diseño, pensé que esta era una excelente oportunidad para usar el patrón mágico de objeto nulo . Así que lo hice, y todo fue suave y limpio. Uno solo tenía que llamar
product.Price.ToString("c")
para mostrar el precio, oproduct.Description.Current
para mostrar la descripción; No se requieren cosas condicionales. Hasta que, un día, la parte interesada solicitó mostrarlo de manera diferente en la API, al tener unnull
JSON. Y también de manera diferente para los administradores de contenido al mostrar "Precio no especificado [Cambiar]". Y tuve que asesinar mi querido patrón de Objetos Nulos, porque ya no era necesario.De la misma manera, tuve que eliminar algunas fábricas abstractas y algunos constructores, terminé reemplazando mi hermoso patrón Facade por llamadas directas y feas, porque las interfaces subyacentes cambiaron dos veces al día durante tres meses, e incluso el Singleton me dejó cuando los requisitos indicaban que el objeto en cuestión tenía que ser diferente según el contexto.
Más de tres semanas de trabajo consistieron en agregar patrones de diseño, luego desgarrarlos un mes después, y mi código finalmente se convirtió en espagueti lo suficiente como para que nadie pudiera mantenerlo, incluido yo mismo. ¿No sería mejor nunca usar esos patrones en primer lugar?
De hecho, tuve que trabajar yo mismo en ese tipo de proyectos donde los requisitos cambian constantemente y están dictados por personas que realmente no tienen en mente la cohesión o la coherencia del producto. En este contexto, no importa cuán ágil sea, encontrará una solución elegante a un problema y, cuando finalmente lo implemente, aprenderá que los requisitos cambiaron tan drásticamente que su solución elegante no encaja. más tiempo.
¿Cuál sería la solución en este caso?
¿No usa ningún patrón de diseño, deja de pensar y escribe código directamente?
Sería interesante hacer una experiencia en la que un equipo escriba código directamente, mientras que otro lo piense dos veces antes de escribir, arriesgándose a tener que tirar el diseño original unos días después: quién sabe, quizás ambos equipos tendrían misma deuda técnica En ausencia de tales datos, solo afirmaría que no se siente bien escribir código sin pensar previamente cuando se trabaja en un proyecto de 20 meses de trabajo.
¿Mantener el patrón de diseño que ya no tiene sentido e intentar agregar más patrones para la situación recién creada?
Esto tampoco parece correcto. Los patrones se utilizan para simplificar la comprensión del código; pon demasiados patrones y el código se convertirá en un desastre.
¿Comienza a pensar en un nuevo diseño que abarque los nuevos requisitos, y luego refactorice lentamente el diseño antiguo en el nuevo?
Como teórico y el que favorece a Agile, estoy totalmente metido en eso. En la práctica, cuando sabe que tendrá que volver a la pizarra cada semana y rehacer gran parte del diseño anterior y que el cliente simplemente no tiene fondos suficientes para pagarlo, ni tiempo suficiente para esperar , esto probablemente no funcionará.
Entonces, ¿alguna sugerencia?
fuente
Respuestas:
Veo algunas suposiciones erróneas en esta pregunta:
Los patrones de diseño no son un fin en sí mismos, deberían servirle a usted, no al revés. Si un patrón de diseño no hace que el código sea más fácil de implementar, o al menos mejor evolucionable (eso significa: más fácil de adaptar a los requisitos cambiantes), entonces el patrón pierde su propósito. No aplique patrones cuando no hacen la "vida" más fácil para el equipo. Si el nuevo patrón de objeto Nulo estaba sirviendo a su amigo durante el tiempo que lo usó, entonces todo estaba bien. Si fuera a eliminarse más tarde, entonces esto también podría estar bien. Si el patrón de objeto nulo ralentizó la implementación (correcta), entonces su uso fue incorrecto. Tenga en cuenta que, a partir de esta parte de la historia, no se puede concluir ninguna causa de "código de espagueti" hasta el momento.
¡Ese no es su trabajo ni su culpa! Su trabajo es preocuparse por la cohesión y la coherencia. Cuando los requisitos cambian dos veces al día, su solución no debe ser sacrificar la calidad del código. Solo dígale al cliente cuánto tiempo lleva, y si cree que necesita más tiempo para lograr que el diseño sea "correcto", agregue un margen de seguridad lo suficientemente grande para cualquier estimación. Especialmente cuando un cliente intenta presionarlo, use el "Principio Scotty" . Y cuando discuta con un cliente no técnico sobre el esfuerzo, evite términos como "refactorización", "pruebas unitarias", "patrones de diseño" o "documentación de código", que son cosas que no entiende y que probablemente considera "innecesarias". sin sentido "porque no ve ningún valor en ello. o al menos comprensible para el cliente (características, subfunciones, cambios de comportamiento, documentos del usuario, correcciones de errores, optimización del rendimiento, etc.).
Honestamente, si "las interfaces subyacentes cambian dos veces al día durante tres meses", entonces la solución no debería ser reaccionar cambiando el código dos veces al día. La solución real es preguntar por qué los requisitos cambian con tanta frecuencia y si es posible hacer un cambio en esa parte del proceso. Quizás un poco más de análisis inicial ayudará. Tal vez la interfaz es demasiado amplia porque el límite entre componentes se elige incorrectamente. A veces es útil pedir más información sobre qué parte de los requisitos son estables y cuáles aún están en discusión (y posponer la implementación de las cosas en discusión). Y a veces algunas personas simplemente tienen que ser "pateadas" por no cambiar de opinión dos veces al día.
fuente
Mi humilde opinión es que no debe evitar o no evitar el uso de patrones de diseño.
Los patrones de diseño son simplemente soluciones conocidas y confiables para problemas generales, que recibieron nombres. No son diferentes en términos técnicos que cualquier otra solución o diseño que se te ocurra.
Creo que la raíz del problema podría ser que su amigo piensa en términos de "aplicar o no aplicar un patrón de diseño", en lugar de pensar en términos de "cuál es la mejor solución que puedo pensar, incluidos, entre otros, los patrones Lo sé".
Tal vez este enfoque lo lleva a usar patrones en formas parcialmente artificiales o forzadas, en lugares donde no pertenecen. Y esto es lo que resulta en un desastre.
fuente
En su ejemplo de uso del patrón Objeto nulo, creo que finalmente falló porque satisfizo las necesidades del programador y no las necesidades del cliente. El cliente necesitaba mostrar el precio en una forma adecuada al contexto. El programador necesitaba simplificar parte del código de visualización.
Entonces, cuando un patrón de diseño no cumple con los requisitos, ¿decimos que todos los patrones de diseño son una pérdida de tiempo o decimos que necesitamos un patrón de diseño diferente?
fuente
Parece que el error fue más eliminar los objetos del patrón que usarlos. En el diseño inicial, el objeto nulo parece haber proporcionado una solución a un problema. Puede que esta no haya sido la mejor solución.
Ser la única persona que trabaja en un proyecto le brinda la oportunidad de experimentar todo el proceso de desarrollo. La gran desventaja es no tener a alguien para que sea tu mentor. Es probable que tomarse el tiempo para aprender y aplicar las mejores o mejores prácticas valga la pena rápidamente. El truco es identificar qué práctica aprender cuando.
El encadenamiento de referencias en la forma product.Price.toString ('c') viola la Ley de Demeter . He visto todo tipo de problemas con esta práctica, muchos de los cuales se relacionan con valores nulos. Un método como product.displayPrice ('c') podría manejar los precios nulos internamente. Del mismo modo, product.Description.Current podría ser manejado por product.displayDescription (), product.displayCurrentDescription (). o product.diplay ('Actual').
El manejo del nuevo requisito para gerentes y proveedores de contenido debe manejarse respondiendo al contexto. Hay una variedad de enfoques que se pueden usar. Los métodos de fábrica podrían usar diferentes clases de productos dependiendo de la clase de usuario en la que se mostrarán. Otro enfoque sería que los métodos de visualización de la clase de producto construyan diferentes datos para diferentes usuarios.
La buena noticia es que tu amigo se da cuenta de que las cosas se están yendo de las manos. Con suerte, tiene el código en control de revisión. Esto le permitirá retirarse de las malas decisiones, que invariablemente tomará. Parte del aprendizaje es probar diferentes enfoques, algunos de los cuales fracasarán. Si puede manejar los próximos meses, puede encontrar enfoques que simplifiquen su vida y limpien los espaguetis. Podría tratar de arreglar una cosa cada semana.
fuente
La pregunta parece estar equivocada en muchos puntos. Pero los flagrantes son:
Muchas personas han dicho correctamente que los patrones de diseño tienen mucho que ver con etiquetar y nombrar una práctica común. Así que piense en una camisa, una camisa tiene un collar, por alguna razón se quita el collar o parte del collar. El nombre y el etiquetado cambian, pero sigue siendo una camisa en esencia. Este es exactamente el caso aquí, pequeños cambios en los detalles, lo que no significa que haya "asesinado" ese patrón. (de nuevo cuenta la redacción extrema)
Según mi experiencia, cuando se presentan requisitos menores, solo necesita cambiar una pequeña parte de la base de código. Algunos pueden ser un poco extravagantes, pero nada demasiado serio para afectar sustancialmente la capacidad de mantenimiento o la legibilidad y, a menudo, bastarán unas pocas líneas de comentarios para explicar la parte extravagante. Esta es una práctica muy común también.
fuente
Hagamos una pausa por un momento y veamos el problema fundamental aquí: la arquitectura de un sistema donde el modelo de arquitectura está demasiado acoplado a las características de bajo nivel en el sistema, lo que hace que la arquitectura se rompa con frecuencia en el proceso de desarrollo.
Creo que debemos recordar que el uso de la arquitectura y los patrones de diseño relacionados con él deben establecerse en un nivel apropiado, y que el análisis de cuál es el nivel correcto no es trivial. Por un lado, puede mantener fácilmente la arquitectura de su sistema en un nivel demasiado alto con restricciones muy básicas como "MVC" o similares, lo que puede conducir a oportunidades perdidas, como en pautas claras y apalancamiento de código, y donde el código de espagueti puede fácilmente florecer en todo ese espacio libre.
Por otro lado, también podría sobre-diseñar su sistema, como al establecer las restricciones en un nivel detallado, donde asume que puede confiar en restricciones que en realidad son más volátiles de lo que espera, rompiendo constantemente sus restricciones y obligándote a remodelar y reconstruir constantemente, hasta que comienzas a desesperarte.
Los cambios en los requisitos de un sistema siempre estarán ahí, en mayor o menor medida. Y los beneficios potenciales de usar arquitectura y patrones de diseño siempre estarán ahí, por lo que no se trata realmente de usar o no patrones de diseño, sino en qué nivel debe usarlos.
Esto requiere que no solo comprenda los requisitos actuales del sistema propuesto, sino que también identifique qué aspectos del mismo pueden verse como propiedades centrales estables del sistema y qué propiedades podrían cambiar durante el curso del desarrollo.
Si descubres que constantemente tienes que luchar con un código de espagueti desorganizado, probablemente no estés haciendo suficiente arquitectura, o en un nivel alto. Si encuentra que su arquitectura se rompe con frecuencia, probablemente esté haciendo una arquitectura demasiado detallada.
El uso de la arquitectura y los patrones de diseño no es algo en lo que pueda simplemente "cubrir" un sistema, como si pintara un escritorio. Son técnicas que deben aplicarse cuidadosamente, en un nivel en el que las restricciones en las que necesita confiar tienen una alta posibilidad de ser estables, y donde estas técnicas realmente valen la pena de modelar la arquitectura e implementar las restricciones / arquitectura / patrones reales como código
Relevante para el tema de una arquitectura demasiado detallada, también puede poner mucho esfuerzo en la arquitectura donde no está dando mucho valor. Consulte la arquitectura basada en el riesgo como referencia. Me gusta este libro: suficiente arquitectura de software , tal vez usted también lo haga.
Editar
Aclaré mi respuesta ya que me di cuenta de que a menudo me expresaba como "demasiada arquitectura", donde realmente quería decir "arquitectura demasiado detallada", que no es exactamente lo mismo. La arquitectura demasiado detallada probablemente a menudo se puede ver como una arquitectura "demasiado", pero incluso si mantiene la arquitectura en un buen nivel y crea el sistema más hermoso que la humanidad haya visto, esto podría ser demasiado esfuerzo en la arquitectura si las prioridades son en características y tiempo de comercialización.
fuente
Su amigo parece estar enfrentando numerosos vientos en contra basados en su anécdota. Eso es desafortunado, y puede ser un ambiente muy difícil para trabajar. A pesar de la dificultad, estaba en el camino correcto de usar patrones para hacer su vida más fácil, y es una pena que haya dejado ese camino. El código de espagueti es el resultado final.
Como hay dos áreas problemáticas diferentes, técnica e interpersonal, abordaré cada una por separado.
Interpersonal
La lucha que está teniendo su amigo es con los requisitos que cambian rápidamente, y cómo eso está afectando su capacidad para escribir código mantenible. Primero diría que los requisitos que cambian dos veces al día, todos los días durante un período de tiempo tan largo es un problema mayor y tiene una expectativa implícita poco realista. Los requisitos están cambiando más rápido de lo que puede cambiar el código. No podemos esperar que el código o el programador se mantengan al día. Este rápido ritmo de cambio es sintomático de una concepción incompleta del producto deseado en un nivel superior. Esto es un problema. Si no saben lo que realmente quieren, perderán mucho tiempo y dinero para nunca obtenerlo.
Puede ser bueno establecer límites para los cambios. Agrupe los cambios en conjuntos cada dos semanas, luego congélelos durante las dos semanas mientras se implementan. Construya una nueva lista para el próximo período de dos semanas. Tengo la sensación de que algunos de estos cambios se superponen o se contradicen (por ejemplo, alternar entre dos opciones). Cuando los cambios son rápidos y furiosos, todos tienen la máxima prioridad. Si deja que se acumulen en una lista, puede trabajar con ellos para organizar y priorizar lo que es más importante para maximizar los esfuerzos y la productividad. Pueden ver que algunos de sus cambios son tontos o menos importantes, lo que le da a su amigo un respiro.
Sin embargo, estos problemas no deberían impedirle escribir un buen código. Un código incorrecto conduce a problemas peores. Puede llevar tiempo refactorizar de una solución a otra, pero el hecho mismo de que sea posible muestra los beneficios de las buenas prácticas de codificación a través de patrones y principios.
En un entorno de cambios frecuentes, la deuda técnica se vencen en algún momento. Es mucho mejor hacer pagos en lugar de tirar la toalla y esperar hasta que sea demasiado grande para superarla. Si un patrón ya no es útil, refactorícelo, pero no vuelva a las formas de codificación de vaquero.
Técnico
Su amigo parece tener una buena comprensión de los patrones de diseño básicos. El objeto nulo es un buen enfoque del problema que enfrentaba. En verdad, sigue siendo un buen enfoque. Donde parece tener desafíos es comprender los principios detrás de los patrones, el por qué de lo que son. De lo contrario, no creo que hubiera abandonado su enfoque.
(Lo que sigue es un conjunto de soluciones técnicas que no se solicitaron en la pregunta original, pero que muestran cómo podríamos adherirnos a los patrones con fines ilustrativos).
El principio detrás del objeto nulo es la idea de encapsular lo que varía . Ocultamos los cambios para no tener que lidiar con eso en ningún otro lado. Aquí, el objeto nulo encapsulaba la varianza en la
product.Price
instancia (lo llamaré unPrice
objeto, y el precio de un objeto nulo seráNullPrice
).Price
es un objeto de dominio, un concepto de negocio. A veces, en nuestra lógica de negocios, todavía no sabemos el precio. Esto pasa. Caso de uso perfecto para el objeto nulo.Price
s tienen unToString
método que genera el precio o una cadena vacía si no se conoce (oNullPrice#ToString
devuelve una cadena vacía). Este es un comportamiento razonable. Entonces los requisitos cambian.Tenemos que enviar una salida
null
a la vista API o una cadena diferente a la vista de los administradores. ¿Cómo afecta esto a nuestra lógica de negocios? Pues no. En la declaración anterior, usé la palabra 'ver' dos veces. Esta palabra probablemente no se pronunció explícitamente, pero debemos entrenarnos para escuchar las palabras ocultas en los requisitos. Entonces, ¿por qué 'ver' importa tanto? Porque nos dice dónde tiene que suceder realmente el cambio : en nuestra opinión.Aparte : si estamos utilizando o no un marco MVC es irrelevante aquí. Si bien MVC tiene un significado muy específico para 'Ver', lo estoy usando en el significado más general (y quizás más aplicable) de un código de presentación.
Entonces realmente necesitamos arreglar esto en la vista. ¿Cómo podríamos hacer eso? La forma más fácil de hacer esto sería una
if
declaración. Sé que el objeto nulo estaba destinado a eliminar todos los ifs, pero debemos ser pragmáticos. Podemos verificar la cadena para ver si está vacía y cambiar:¿Cómo afecta esto a la encapsulación? La parte más importante aquí es que la lógica de vista está encapsulada en la vista . De esta forma, podemos mantener nuestros objetos de dominio / lógica de negocio completamente aislados de los cambios en la lógica de vista. Es feo, pero funciona. Sin embargo, esta no es la única opción.
Podríamos decir que nuestra lógica de negocios ha cambiado un poco porque queremos generar cadenas predeterminadas si no se establece un precio. Podemos hacer un pequeño ajuste a nuestro
Price#ToString
método (en realidad, crear un método sobrecargado). Podemos aceptar un valor de retorno predeterminado y devolverlo si no se establece un precio:Y ahora nuestro código de vista se convierte en:
El condicional se ha ido. Sin embargo, hacer esto demasiado podría proliferar los métodos de casos especiales en los objetos de su dominio, por lo que esto solo tiene sentido si solo habrá unos pocos casos de esto.
En su lugar, podríamos crear un
IsSet
métodoPrice
que devuelva un valor booleano:Ver lógica:
Vemos el retorno del condicional en la vista, pero el caso es más fuerte para la lógica de negocios que indica si el precio está establecido. Podemos usarlo en
Price#IsSet
otro lugar ahora que lo tenemos disponible.Finalmente, podemos encapsular la idea de presentar un precio completamente en un asistente para la vista. Esto ocultaría el condicional, al tiempo que conserva el objeto de dominio tanto como nos gustaría:
Ver lógica:
Hay muchas más formas de realizar los cambios (podríamos generalizar
PriceStringHelper
en un objeto que devuelve un valor predeterminado si una cadena está vacía), pero estas son algunas rápidas que preservan (en su mayor parte) tanto los patrones como los principios, como así como el aspecto pragmático de hacer tal cambio.fuente
La complejidad de un patrón de diseño puede morderte si el problema que se suponía que debía resolver desaparece repentinamente. Lamentablemente, debido al entusiasmo y la popularidad de los patrones de diseño, este riesgo rara vez se hace explícito. La anécdota de tu amigo ayuda mucho a mostrar cómo los patrones no dan resultado. Jeff Atwood tiene algunas palabras de elección sobre el tema.
Documente los puntos de variación (son riesgos) en los requisitos
Muchos de los patrones de diseño más complejos (objeto nulo no tanto) contienen el concepto de variaciones protegidas , que es "Identificar puntos de variación o inestabilidad pronosticados; asignar responsabilidades para crear una interfaz estable a su alrededor". Adaptador, visitante, fachada, capas, observador, estrategia, decorador, etc., todos explotan este principio. "Pagan" cuando el software necesita extenderse en la dimensión de la variabilidad esperada, y los supuestos "estables" permanecen estables.
Si sus requisitos son tan inestables que sus "variaciones pronosticadas" siempre son incorrectas, entonces los patrones que aplique le causarán dolor o, en el mejor de los casos, complejidad innecesaria.
Craig Larman habla de dos oportunidades para aplicar variaciones protegidas:
Se supone que ambos deben estar documentados por los desarrolladores, pero probablemente debería tener el compromiso del cliente con los puntos de variación.
Para gestionar el riesgo, podría decir que cualquier patrón de diseño que aplique PV debe rastrearse hasta un punto de variación en los requisitos firmados por el cliente. Si un cliente cambia un punto de variación en los requisitos, su diseño puede tener que cambiar radicalmente (porque probablemente invirtió en diseño [patrones] para respaldar esa variación). No es necesario explicar la cohesión, el acoplamiento, etc.
Por ejemplo, su cliente quiere que el software funcione con tres sistemas de inventario heredados diferentes. Ese es un punto de variación alrededor del cual diseñas. Si el cliente cae ese requisito, entonces, por supuesto, tiene un montón de infraestructura de diseño inútil. El cliente necesita saber que los puntos de variación cuestan algo.
CONSTANTS
en el código fuente son una forma simple de PVOtra analogía a su pregunta sería preguntar si usar
CONSTANTS
en el código fuente es una buena idea. Refiriéndonos a este ejemplo , digamos que el cliente eliminó la necesidad de contraseñas. Por lo tanto, laMAX_PASSWORD_SIZE
extensión constante a lo largo de su código sería inútil e incluso un obstáculo para el mantenimiento y la legibilidad. ¿Culparías al usoCONSTANTS
como la razón?fuente
Creo que al menos en parte depende de la naturaleza de su situación.
Mencionaste requisitos constantemente cambiantes. Si el cliente dice "Quiero que esta aplicación de apicultura también funcione con avispas", parece que ese es el tipo de situación en la que un diseño cuidadoso ayudaría al progreso, no lo obstaculizaría (especialmente si considera que en el futuro podría querer mantener moscas de la fruta también.)
Por otro lado, si la naturaleza del cambio es más como "Quiero que esta aplicación de apicultura administre la nómina de mi conglomerado de lavandería", ninguna cantidad de código lo sacará de su agujero.
No hay nada intrínsecamente bueno en los patrones de diseño. Son herramientas como cualquier otra: solo las usamos para facilitar nuestro trabajo a mediano y largo plazo. Si una herramienta diferente (como la comunicación o la investigación ) es más útil, entonces la usamos.
fuente