¿Deberíamos evitar usar patrones de diseño en proyectos en constante cambio?

32

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, o product.Description.Currentpara 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 un nullJSON. 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?

Arseni Mourzenko
fuente
43
Creo que este es un falso dilema. Por su propia admisión, el código es ahora un plato de espagueti que no se puede mantener. Eso no es culpa de los patrones de software; es la incapacidad de tu amigo para usar esos patrones correctamente, de manera que aumente la capacidad de mantenimiento, no lo disminuya. Cuando pueda encontrar ejemplos específicos, publicaré una respuesta adecuada.
Robert Harvey
55
Además, FWIW, cualquier cliente que no tenga cierta tolerancia a los costos de deriva probablemente no debería estar haciendo Ágil, a menos que haya costos incorporados en los costos para los requisitos de cambio.
Robert Harvey
55
Sospecho que sin los patrones de diseño, el código habría alcanzado un estado imposible de mantener mucho antes
Steven A. Lowe
28
Esta pregunta no tiene ningún sentido. La única forma de "evitar patrones de diseño" es no escribir ningún software. Cosas como "Patrón XYZ" son solo nombres dados a estrategias de codificación comunes para permitirnos a los programadores transmitir información y consejos sobre nuestra estructura de código y opciones entre nosotros de manera más conveniente. Cualquier opción de diseño en su código podría recibir un nombre y llamarse un "patrón de diseño", aunque no necesariamente uno ampliamente conocido (a menos, supongo, que esté orgulloso de su diseño único y lo suficientemente motivado para darle un nombre y blog sobre o algo)
Jason C
9
Es decir, puede evitar llamar a su pie "pie", pero todavía tiene ese pie, es más difícil hablar de ello con alguien si no lo llama "pie". Puede pensar que está "evitando patrones de diseño", pero si se le ocurre un diseño decente que funcione, retroceda y eche un vistazo, probablemente descubra que su diseño acaba de encajar en uno de los patrones con nombre comunes de todos modos, ya sea que lo llames así o no.
Jason C

Respuestas:

86

Veo algunas suposiciones erróneas en esta pregunta:

  • El código con patrones de diseño, aunque se aplica correctamente, necesita más tiempo para implementarse que el código sin esos patrones.

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.

  • El cliente tiene la culpa porque no tiene antecedentes técnicos y no le importa la cohesión o la coherencia del producto.

¡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.).

  • la solución para los requisitos de cambio rápido es cambiar rápidamente el código

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.

Doc Brown
fuente
30
+1: no puede resolver los problemas de las personas con soluciones técnicas.
Telastyn
1
+1, pero "o al menos mejor evolucionable (eso significa: más fácil de adaptar a los requisitos cambiantes)" - Calificaría esto con requisitos que cambian razonablemente , ¿verdad?
Fuhrmanator
1
@Fuhrmanator: Creo que esto es difícil de discutir en términos generales. En mi humilde opinión, es obvio que ningún patrón de diseño lo ayudará cuando su primer requisito sea "necesitamos un procesador de textos" y esto cambia a "necesitamos un simulador de vuelo". Para cambios de requisitos menos drásticos, no siempre es fácil decidir qué lo ayudará a mantener su software en evolución. Lo mejor es que, en mi humilde opinión, no aplique demasiados patrones, sino algunos principios, principalmente los principios SÓLIDOS y YAGNI. Y estoy 100% de acuerdo con Telastyn, cuando los requisitos cambian con demasiada frecuencia, probablemente no sea un problema técnico.
Doc Brown
44
+1 - "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 " . Si constantemente recibe nuevas instrucciones, es mejor sentarse con todos los interesados ​​y reafirmar cuáles son las expectativas. Calcule sus diferencias y, con suerte, no pierda el tiempo y el dinero de todos al darle al proyecto un objetivo más claro.
krillgar
1
@Cornelius Doc Brown dijo que es difícil sin concreción. Los requisitos para pasar de un procesador de texto a un simulador de vuelo no serían razonables; Ningún patrón de diseño ayudaría. Hay una gran cantidad de áreas grises entre su ejemplo y, por ejemplo, agregar un nuevo formato de archivo a la función Guardar de un procesador de textos (que es muy razonable). Sin detalles, es difícil discutirlo. Además, no es que uno no quiera cambiar. Es que tales cambios son difíciles, si ya hizo una elección de diseño temprano en función de un requisito. El procesador de texto vs. simulador de vuelo es un gran ejemplo de elección temprana.
Fuhrmanator
43

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.

Aviv Cohn
fuente
13
+1 "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 pueda imaginar". Exactamente. Las personas quedan tan atrapadas en los patrones de diseño con nombre que se olvidan de que estos no son más que nombres dados a diversas estrategias para que a los programadores nos resulte más fácil comunicarnos entre nosotros sobre nuestro código y nuestras opciones de diseño. Esta actitud es muy a menudo asociada con el intento de forzar "patrones" inapropiadas sobre los problemas que no necesariamente benefician - gran lío.
Jason C
14

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?

BobDalgleish
fuente
9

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.

BillThor
fuente
2
También podría indicar, al ver que "tiene que" violar la ley de Demeter, que el modelo utilizado en la superficie no era adecuado. ¿Por qué el "modelo de vista" (usado en un sentido amplio) no solo tiene la descripción para mostrar? (Es decir, ¿por qué hay algo más que la descripción actual en el nivel de la interfaz de usuario?) La capa empresarial podría preparar un objeto debidamente relleno para la capa de la interfaz de usuario que ya tiene contenido diferente dependiendo de si es el administrador o no.
Cornelius
7

La pregunta parece estar equivocada en muchos puntos. Pero los flagrantes son:

  • Para el Patrón de objeto nulo que mencionó, después de que los requisitos cambiaron, cambia un poco del código. Eso está bien, pero no significa que 'asesines' el Patrón de objetos nulos (por cierto, ten cuidado con tu redacción, esto suena demasiado extremo, algunas personas demasiado paranoicas no verán esto como algo gracioso).

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)

  • El diseño del que habló es malo porque cuando los cambios en los requisitos son cosas menores, se hacen cambios masivos de diseño estratégico en todos los lugares. A menos que cambie el problema comercial de alto nivel, no puede justificar hacer un cambio masivo de diseño.

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.

InformadoA
fuente
7

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.

Alex
fuente
+1 Estas son muy buenas ideas sobre el nivel muy alto, creo que debes mirar en un sistema, pero como dijiste, requiere mucha experiencia en diseño de software.
Samuel
4

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.Priceinstancia (lo llamaré un Priceobjeto, y el precio de un objeto nulo será NullPrice). Pricees 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. Prices tienen un ToStringmétodo que genera el precio o una cadena vacía si no se conoce (o NullPrice#ToStringdevuelve una cadena vacía). Este es un comportamiento razonable. Entonces los requisitos cambian.

Tenemos que enviar una salida nulla 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 ifdeclaració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:

if(product.Price.ToString("c").Length == 0) { // one way of many
    writer.write("Price unspecified [Change]");
} else {
    writer.write(product.Price.ToString("c"));
}

¿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#ToStringmétodo (en realidad, crear un método sobrecargado). Podemos aceptar un valor de retorno predeterminado y devolverlo si no se establece un precio:

class Price {
    ...
    // A new ToString method
    public string ToString(string c, string default) {
        return ToString(c);
    }
    ...
}

class NullPrice {
    ...
    // A new ToString method
    public string ToString(string c, string default) {
        return default;
    }
    ...
}

Y ahora nuestro código de vista se convierte en:

writer.write(product.Price.ToString("c", "Price unspecified [Change]"));

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 IsSetmétodo Priceque devuelva un valor booleano:

class Price {
    ...
    public bool IsSet() {
        return return true;
    }
    ...
}

class NullPrice {
    ...
    public bool IsSet() {
        return false;
    }
    ...
}

Ver lógica:

if(product.Price.IsSet()) {
    writer.write(product.Price.ToString("c"));
} else {
    writer.write("Price unspecified [Change]");
}

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#IsSetotro 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:

class PriceStringHelper {
    public PriceStringHelper() {}

    public string PriceToString(Price price, string default) {
        if(price.IsSet()) { // or use string length to not change the Price class at all
           return price.ToString("c");
        } else {
            return default;
        }
    }
}

Ver lógica:

writer.write(new PriceStringHelper().PriceToString(product.Price, "Price unspecified [Change]"));

Hay muchas más formas de realizar los cambios (podríamos generalizar PriceStringHelperen 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.

cbojar
fuente
3

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:

  • puntos de variación : en el sistema actual o los requisitos actuales, como múltiples interfaces que deben ser compatibles, y
  • puntos de evolución : puntos de variación especulativos que no están presentes en los requisitos existentes.

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 PV

Otra analogía a su pregunta sería preguntar si usar CONSTANTSen 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, la MAX_PASSWORD_SIZEextensió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 uso CONSTANTScomo la razón?

Fuhrmanator
fuente
2

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.

Benjamin Hodgson
fuente