¿La solución debería ser lo más genérica posible o lo más específica posible?

122

Supongamos que tengo una entidad que tiene el atributo "tipo". Podría haber más de 20 tipos posibles.

Ahora me piden que implemente algo que permita cambiar el tipo de A- > B, que es el único caso de uso.

Entonces, ¿debería implementar algo que permita cambios arbitrarios de tipo siempre y cuando sean tipos válidos? ¿O SOLO debería permitir que cambie de A- > B según el requisito y rechazar cualquier otro cambio de tipo como B- > A o A- > C?

Puedo ver los pros y los contras de ambos lados, donde una solución genérica significaría menos trabajo en caso de que surja un requisito similar en el futuro, pero también significaría más posibilidades de salir mal (aunque controlamos al 100% el persona que llama en este punto).
Una solución específica es menos propensa a errores, pero requiere más trabajo en el futuro si surge un requisito similar.

Sigo escuchando que un buen desarrollador debe tratar de anticipar el cambio y diseñar el sistema para que sea fácil de extender en el futuro, lo que parece que una solución genérica es el camino a seguir.

Editar:

Agregando más detalles a mi ejemplo no tan específico: La solución "genérica" en este caso requiere menos trabajo que la solución "específica", ya que la solución específica requiere validación en el tipo anterior y en el tipo nuevo, mientras que la solución genérica solo necesita validar el nuevo tipo.

    
pregunta LoveProgramming 30.11.2017 - 19:31
fuente

14 respuestas

296

Mi regla de oro:

  1. la primera vez que encuentre el problema, solo resuelva el problema específico (este es el principio YAGNI )
  2. la segunda vez que te encuentres con el mismo problema, considera generalizar el primer caso, si no es mucho trabajo
  3. una vez que tenga tres casos específicos en los que podría usar la versión generalizada, entonces debería comenzar a planificar realmente la versión generalizada. A estas alturas, debería entender el problema lo suficientemente bien como para poder generalizarlo.

Por supuesto, esta es una guía y no una regla estricta: la respuesta real es usar su mejor criterio, caso por caso.

    
respondido por el Daniel Pryden 30.11.2017 - 19:37
fuente
95
  

Una solución específica [...] requiere más trabajo en el futuro si se necesitan requisitos similares

He escuchado este argumento varias docenas de veces y, según mi experiencia, con frecuencia resulta ser una falacia. Si generaliza ahora o más adelante, cuando surja el segundo requisito similar, el trabajo será casi el mismo en total. Por lo tanto, no tiene ningún sentido invertir un esfuerzo adicional en generalizar, cuando no se sabe que este esfuerzo tendrá resultados.

(Obviamente, esto no se aplica cuando una solución más general es menos complicada y requiere menos esfuerzo que una específica, pero según mi experiencia, estos son casos raros. Este tipo de escenario se editó posteriormente en la pregunta, y no es de lo que trata mi respuesta).

Cuando aparece el "segundo caso similar", es hora de empezar a pensar en generalizar. Entonces será mucho más fácil generalizar correctamente , porque este segundo requisito le brinda un escenario en el que puede validar si hizo las cosas correctas genéricas. Cuando intentas generalizar solo para un caso, estás disparando en la oscuridad. Hay muchas posibilidades de que, en general, genere en exceso ciertas cosas que no necesitan ser generalizadas, y omita otras partes que deberían. Y cuando surge un segundo caso y te das cuenta de que has generalizado las cosas incorrectas, tienes mucho más trabajo que hacer para solucionar esto.

Así que recomiendo retrasar cualquier tentación de hacer las cosas "por si acaso". Este enfoque solo conducirá a más esfuerzos de trabajo y mantenimiento cuando perdió la ocasión de generalizar tres, cuatro o más veces y luego tiene un montón de código de aspecto similar (por lo tanto, duplicado) para mantener.

    
respondido por el Doc Brown 30.11.2017 - 21:06
fuente
63

TL; DR: depende de lo que estés tratando de resolver.

He tenido una conversación similar con mis Gramps sobre esto, mientras hablábamos sobre cómo Func y Action en C # son increíbles. My Gramps es un programador de temporizador muy antiguo, que ha estado relacionado con los códigos fuente desde que el software se ejecutó en computadoras que ocupaban toda una sala.

Cambió de técnico varias veces en su vida. Escribió código en C, COBOL, Pascal, BASIC, Fortran, Smalltalk, Java y finalmente comenzó C # como pasatiempo. Aprendí a programar con él, sentándome en su regazo mientras era más que un demonio, eliminando mis primeras líneas de código en el editor azul del SideKick de IBM. Cuando tenía 20 años, ya había pasado más tiempo programando que jugando afuera.

Esos son algunos de mis recuerdos, así que discúlpeme si no soy exactamente práctico mientras los vuelvo a contar. Me gustan mucho esos momentos.

Eso es lo que me dijo:

"¿Deberíamos buscar la generalización de un problema o resolverlo en el ámbito específico, preguntas? Bueno, esa es una ... pregunta".

Gramps hizo una pausa para pensar en ello por un breve momento, mientras fija la posición de sus lentes en su cara. Estaba jugando un juego de match-3 en su computadora, mientras escuchaba un LP de Deep Purple en su antiguo sistema de sonido.

"Bueno, eso dependería del problema que estés tratando de resolver", me dijo. "Es tentador creer que existe una solución única y sagrada para todas las opciones de diseño, pero no la hay. La Arquitectura de software es como el queso, ya ves".

"... ¿Queso, Gramps?"

"No importa lo que pienses sobre tu favorito, siempre habrá alguien que piense que huele mal".

Parpadeé confundido por un momento, pero antes de que pudiera decir algo, Gramps continuó.

"Cuando estás construyendo un automóvil, ¿cómo seleccionas el material para una pieza?"

"Yo ... supongo que depende de los costos involucrados y de lo que debería hacer la parte, supongo."

"Depende del problema que la parte está tratando de resolver. No fabricará una llanta de acero ni un parabrisas de cuero. Escogerá el material que mejor resuelva el problema que tiene a mano. Ahora, ¿Qué es una solución genérica? ¿O una específica? ¿A qué problema, a qué caso de uso? ¿Debería utilizar un enfoque funcional completo para dar la máxima flexibilidad a un código que se usará solo una vez? ¿Debe escribir un código muy especializado? Código frágil para una parte de su sistema que verá muchos y muchos usos, y posiblemente muchos cambios. Las opciones de diseño son como los materiales que selecciona para una parte en un automóvil o la forma del ladrillo Lego que elige para construir. Una casita. ¿Cuál es el mejor ladrillo de Lego? "

El programador de mayor edad tomó un pequeño modelo de tren Lego que tiene sobre su mesa antes de continuar.

"Solo puede responder eso si sabe para qué necesita ese ladrillo. ¿Cómo diablos sabrá si la solución específica es mejor que la genérica, o viceversa, si ¿Ni siquiera sabe qué problema está tratando de resolver? No puede ver más allá de una opción que no entiende ".

"... ¿Acaba de citar The Matrix? "

"¿Qué?"

"Nada, continúa"

"Bueno, supongamos que estás intentando construir algo para el Sistema Nacional de Facturas. Sabes cómo se ve esa API infernal y sus treinta mil líneas de archivos XML desde dentro. ¿Cómo sería una solución 'genérica' para crear ese archivo? El archivo está lleno de parámetros opcionales, llenos de casos que solo deberían usar sucursales muy específicas del negocio. En la mayoría de los casos, puede ignorarlos de manera segura. No es necesario crear un sistema de facturación genérico si es el único. Lo que alguna vez venderá es zapatos. Simplemente cree un sistema para vender zapatos y conviértalo en el mejor sistema de facturas para vender zapatos. Ahora, si tuviera que crear un sistema de facturas para cualquier tipo de cliente, de una forma más Aplicación general: para revender como un sistema de ventas genérico independiente, por ejemplo, ahora es interesante implementar aquellas opciones que solo se usan para Gas, alimentos o alcohol. Ahora esos son posibles casos de uso. Antes de que fueran solo algunos casos hipotéticos No usar , un d no desea implementar No usar casos. No usar es el hermano pequeño de No necesita . "

Gramps volvió a poner el tren lego en su lugar y regresó a su juego de combinaciones.

"Entonces, para poder elegir una solución genérica o específica para un problema determinado, primero debe comprender qué diablos es ese problema. De lo contrario, solo está adivinando, y adivinar es el trabajo de los gerentes, no de los programadores. . Como casi todo en informática, depende ".

Entonces, ahí lo tienen. "Depende". Esa es probablemente la expresión de dos palabras más poderosa cuando se piensa en el diseño de software.

    
respondido por el T. Sar 30.11.2017 - 21:01
fuente
14

Principalmente, debe intentar anticipar si es probable que ocurra tal cambio, no solo una posibilidad remota en algún lugar de la línea.

Si no es así, generalmente es mejor buscar la solución simple ahora y extenderla más tarde. Es muy posible que tengas una imagen mucho más clara de lo que se necesita entonces.

    
respondido por el doubleYou 30.11.2017 - 19:38
fuente
13

Si estás trabajando en un dominio que es nuevo para ti, entonces la Regla de tres que Daniel Pryden mencionado definitivamente debe aplicarse. Después de todo, ¿cómo se supone que construyas abstracciones útiles si eres un principiante en esa área? Las personas a menudo son seguras de sí mismas en su capacidad para atrapar abstracciones, aunque rara vez es el caso. Según mi experiencia, la abstracción prematura no es menos malvada que la duplicación de código. Las abstracciones erróneas son realmente dolorosas de comprender. A veces, incluso más doloroso refactorizar.

Hay un libro que trata sobre un punto en el que un desarrollador de área desconocida está trabajando. Se compone de dominios específicos Con abstracciones útiles extraídas.

    
respondido por el Zapadlo 30.11.2017 - 20:00
fuente
4

Dada la naturaleza del cuerpo de su pregunta, asumiendo que lo entendí correctamente, realmente veo esto como una pregunta de diseño de las características del sistema central más que una pregunta sobre soluciones genéricas frente a soluciones específicas.

Y cuando se trata de las características y capacidades del sistema central, las más confiables son las que no están allí. Vale la pena equivocarse por el lado del minimalismo, especialmente considerando que generalmente es más fácil agregar funcionalidades de forma centralizada, mucho tiempo deseado, que eliminar funcionalidades problemáticas, indeseables, con numerosas dependencias porque ha hecho mucho más difícil trabajar con el sistema. de lo que debe ser al mismo tiempo que plantea interminables preguntas de diseño con cada nueva función.

De hecho, sin una fuerte anticipación de si esto será necesario con frecuencia en el futuro, trataría de evitar ver esto como un reemplazo de tipo de A a B si es posible, y en su lugar, simplemente lo busco como una manera de transformar El estado de a Por ejemplo, establezca algunos campos en A para hacer que se convierta en morph y aparezca como B para el usuario sin cambiar realmente a un 'tipo' diferente de cosa: potencialmente podría hacer que A store B use de forma privada funciones de composición y llamada en B cuando el estado de A está configurado para indicar que debería imitar a B para simplificar la implementación si es posible. Esa debería ser una solución muy simple y mínimamente invasiva.

De todos modos, haciéndome eco de muchos otros, sugeriría equivocarse al evitar soluciones genéricas en este caso, pero más aún porque supongo que esto se debe a la posibilidad de agregar una capacidad muy audaz al sistema central, y hay Yo sugeriría errar por el lado de omitirlo, especialmente por ahora.

    
respondido por el user204677 30.11.2017 - 21:20
fuente
3

Es difícil dar una respuesta genérica a este problema específico ;-)

Cuanto más genérico sea, más tiempo ganará para futuros cambios. Por ejemplo, por este motivo, muchos programas de juegos utilizan el patrón de componente de la entidad de construir un sistema de tipo muy elaborado pero rígido de los personajes y objetos en el juego.

Por otra parte, hacer algo genérico requiere una inversión inicial de tiempo y esfuerzo en diseño, que es mucho más alto que para algo muy específico. Esto conlleva el riesgo de una ingeniería excesiva, e incluso de perderse en los posibles requisitos futuros.

Siempre vale la pena ver si hay una generalización natural que le proporcionará un gran avance. Sin embargo, al final, se trata de una cuestión de equilibrio, entre el esfuerzo que puede realizar ahora y el esfuerzo que podría necesitar en el futuro.

    
respondido por el Christophe 30.11.2017 - 19:53
fuente
3
  1. Híbrido. Esto no tiene que ser una pregunta o una u otra. Puede diseñar la API para conversiones de tipo general mientras implementa solo la conversión específica que necesita ahora. (Solo asegúrese de que si alguien llama a su API general con una conversión no admitida, falla con un estado de error "no compatible".)

  2. Pruebas. Para la conversión A- > B, tendré que escribir una (o una pequeña cantidad) de pruebas. Para una conversión x > y genérica, podría tener que escribir toda una matriz de pruebas. Eso es mucho más trabajo, incluso si todas las conversiones comparten una sola implementación genérica.

    Si, por otro lado, resulta que hay una forma genérica de probar todas las conversiones posibles, entonces no hay mucho más trabajo y es posible que me incline a buscar una solución genérica antes.

  3. Acoplamiento. Un convertidor de A a B puede necesitar necesariamente conocer los detalles de implementación de A y B (acoplamiento apretado). Si A y B siguen evolucionando, esto significa que podría tener que volver a visitar el convertidor (y sus pruebas), lo que apesta, pero al menos está limitado a A y B.

    Si hubiera optado por una solución genérica que necesita acceso a los detalles de todos los tipos, incluso a medida que C y D evolucionan, es posible que tenga que seguir modificando el convertidor genérico (y un montón de pruebas), lo que puede ralentizar Bajé aunque nadie necesita convertir a una C o una D .

    Si tanto las conversiones genéricas como las específicas pueden implementarse de una manera que está unida a los detalles de los tipos, entonces no me preocuparía. Si uno de estos se puede hacer de manera flexible, pero el otro requiere un acoplamiento firme, entonces ese es un argumento sólido para el enfoque de acoplamiento flexible desde la puerta.

respondido por el Adrian McCarthy 30.11.2017 - 23:57
fuente
3
  

¿La solución debería ser lo más genérica posible o lo más específica posible?

Esa no es una pregunta que pueda responderse.

Lo mejor que puedes obtener razonablemente es un par de heurísticas para decidir qué tan general o específico es hacer una solución determinada. Al trabajar en algo como el proceso a continuación, generalmente la aproximación de primer orden es correcta (o lo suficientemente buena). Cuando no lo sea, es probable que la razón sea demasiado específica del dominio para cubrirla útilmente aquí.

  1. Aproximación de primer orden: la regla usual de YAGNI de tres, como lo describe Daniel Pryden, Doc Brown, et al .

    Esta es una heurística generalmente útil porque es probablemente lo mejor que puedes hacer, no depende del dominio y otras variables.

    Entonces, la presunción inicial es: hacemos lo más específico.

  2. Aproximación de segundo orden: según su conocimiento experto del dominio de la solución , usted dice

      

    La solución "genérica" en este caso requiere menos trabajo que la solución "específica"

    para que podamos reinterpretar YAGNI como recomendación, evitamos el trabajo innecesario , en lugar de evitar generalidades innecesarias. Por lo tanto, podríamos modificar nuestra presunción inicial y, en su lugar, hacer lo más fácil .

    Sin embargo, si su conocimiento del dominio de la solución indica que la solución más fácil es probable que abra muchos errores, o que sea difícil de probar adecuadamente, o cause cualquier otro problema, entonces ser más fácil de codificar no es necesariamente una razón suficiente. para cambiar nuestra elección original.

  3. Aproximación de tercer orden: ¿su conocimiento de dominio de problemas sugiere que la solución más fácil es realmente correcta, o está permitiendo que muchas transiciones sin sentido o incorrectas?

    Si la solución fácil pero genérica parece problemática, o no confía en su capacidad para juzgar estos riesgos, hacer el trabajo extra y continuar con su estimación inicial probablemente sea mejor.

  4. Aproximación de cuarto orden: ¿su conocimiento del comportamiento del cliente, o cómo se relaciona esta característica con otros, o las prioridades de gestión del proyecto, o ... alguna otra consideración no estrictamente técnica modifica su decisión de trabajo actual?

respondido por el Useless 01.12.2017 - 14:56
fuente
2

Esta no es una pregunta fácil de responder con una respuesta simple. Muchas respuestas han dado heurísticas basadas en una regla de 3, o algo similar. Ir más allá de tales reglas de oro es difícil.

Para realmente realmente responder a su pregunta, debe tener en cuenta que lo más probable es que su trabajo no implemente algo que cambie A- > B. Si usted es un contratista, tal vez ese sea el requisito, pero si es un empleado, se lo contrató para realizar muchas tareas más pequeñas para la empresa. Cambiar A- > B es solo una de esas tareas. A su empresa también le importará qué tan bien se pueden hacer los cambios futuros, incluso si eso no se indica en la solicitud. Para encontrar "TheBestImplementation (tm)", debe mirar la imagen más grande de lo que realmente se le pide que haga, y luego usarla para interpretar la pequeña solicitud que le dieron para cambiar A- > B.

Si eres un programador de entrada de bajo nivel recién salido de la universidad, a menudo es recomendable hacer exactamente lo que se te ordenó. Si fuiste contratado como arquitecto de software con 15 años de experiencia, generalmente es recomendable pensar en cosas grandes. Cada trabajo real se ubicará entre "hacer exactamente lo que es la tarea limitada" y "pensar en el panorama general". Sentirás dónde encaja tu trabajo en ese espectro si hablas lo suficiente con la gente y trabajas lo suficiente por ella.

Puedo dar algunos ejemplos concretos en los que su pregunta tiene una respuesta definitiva basada en el contexto. Considere el caso en el que está escribiendo software crítico para la seguridad. Esto significa que tiene un equipo de pruebas que se puso de pie para asegurarse de que el producto se desempeña según lo prometido. Algunos de estos equipos de prueba deben probar cada ruta posible a través del código. Si habla con ellos, puede encontrar que si generaliza el comportamiento, agregará $ 30,000 a sus costos de prueba porque tendrán que probar todas esas rutas adicionales. En ese caso, no agrega funcionalidad generalizada, incluso si tiene que duplicar el trabajo 7 u 8 veces por ello. Ahorre dinero a la empresa y haga exactamente lo que indica la solicitud.

En el otro lado, considera que estás creando una API para permitir que los clientes accedan a los datos en un programa de base de datos que hace tu empresa. Un cliente solicita permitir cambios A- > B. Las API suelen tener un aspecto de esposas doradas: una vez que agregas funcionalidad a una API, normalmente no debes eliminar esa funcionalidad (hasta el siguiente número de versión principal). Es posible que muchos de sus clientes no estén dispuestos a pagar el costo de la actualización al siguiente número de versión principal, por lo que es posible que se quede estancado con la solución que elija durante mucho tiempo. En este caso, altamente recomiendo crear la solución genérica desde el principio. Realmente no quieres desarrollar una API mala llena de comportamientos únicos.

    
respondido por el Cort Ammon 01.12.2017 - 02:37
fuente
1

Hmm ... no hay mucho contexto para una respuesta ... repitiendo respuestas anteriores, "Depende".

En cierto sentido, tienes que recurrir a tu experiencia. Si no es tuyo, entonces alguien más mayor en el dominio. Podrías objetar acerca de lo que indican los criterios de aceptación. Si es algo como "el usuario debería poder cambiar el tipo de" A "a" B "" en comparación con "el usuario debería poder cambiar el tipo de su valor actual a cualquier valor alternativo permitido".

Los criterios de aceptación frecuentes están sujetos a interpretación, pero un buen personal de control de calidad puede escribir los criterios adecuados para la tarea en cuestión, minimizando la interpretación necesaria.

¿Existen restricciones de dominio que no permiten cambiar de "A" a "C" o cualquier otra opción, pero solo de "A" a "B"? ¿O es solo un requisito específico que no es "prospectivo"?

Si el caso general fuera más difícil, iría a preguntar antes de comenzar el trabajo, pero en su caso, si pudiera "predecir" que en el futuro vendrían otras solicitudes de cambio de tipo, estaría tentado a : a) escribir algo reutilizable para el caso general, y b) envuélvalo en un condicional que solo permita A - > B por ahora.

Lo suficientemente fácil de verificar para el caso actual en las pruebas automatizadas, y lo suficientemente fácil para abrirse a otras opciones más adelante si / cuando surjan diferentes casos de uso.

    
respondido por el railsdog 01.12.2017 - 06:56
fuente
1

Para mí, una guía que establecí hace un tiempo es: "Para requisitos hipotéticos, solo escriba código hipotético". Es decir, si anticipa requisitos adicionales, debe pensar un poco sobre cómo implementarlos y estructurar su código actual para que no se bloquee de esa manera.

Pero no escriba el código real para estos ahora, solo piense un poco sobre lo que haría. De lo contrario, por lo general, hará las cosas innecesariamente complejas, y probablemente se molestará más adelante cuando los requisitos reales difieran de lo que usted anticipó.

Cuatro su ejemplo: si tiene todos los usos del método de conversión bajo su control, puede llamarlo convertAToB por ahora y planea usar un "cambio de nombre" en el IDE para cambiar el nombre si necesita más información. Funcionalidad posterior. Sin embargo, si el método de conversión es parte de una API pública, esto podría ser bastante diferente: ser específico bloquearía la generalización más adelante, ya que es difícil cambiar el nombre de las cosas en ese caso.

    
respondido por el Hans-Peter Störr 05.12.2017 - 12:46
fuente
0
  

Sigo escuchando que un buen desarrollador debe intentar anticipar el cambio y diseñar el sistema para que sea fácil de extender en el futuro,

En principio, sí. Pero esto no lleva necesariamente a soluciones genéricas.

Hay dos tipos de temas en el desarrollo de software, en lo que a mí respecta, donde debería anticipar cambios futuros:

  • Bibliotecas destinadas a ser utilizadas por terceros, y
  • arquitectura general del software.

El primer caso se resuelve observando su cohesión / acoplamiento, inyección de dependencia o lo que sea. El segundo caso es a un nivel más abstracto, por ejemplo, elegir una arquitectura orientada a servicios en lugar de un gran blob monolótico de código para una aplicación grande.

En su caso, está solicitando una solución específica para un problema específico, que no tiene un impacto previsible en el futuro. En este caso, YAGNI y DRY son buenos lemas para disparar:

  • YAGNI (no lo vas a necesitar) te dice que implementes las cosas básicas y absolutamente mínimas que necesitas y que uses ahora mismo . Significa implementar el mínimo que hace que su conjunto de pruebas actual cambie de rojo a verde, si debe usar el desarrollo de estilo TDD / BDD / FDD. Ni una sola línea más.
  • DRY (no se repita) significa que si vuelve a tener un problema similar, luego analice detenidamente si necesita una solución genérica.

Combinado con otras prácticas modernas (como una buena cobertura de prueba para poder refactorizar de manera segura), esto significa que usted termina con un código de escritura rápido y simple que crece según sea necesario.

  

¿cuál suena como una solución genérica es el camino a seguir?

No, parece que debería tener entornos de programación, lenguajes y herramientas que hacen que sea fácil y divertido refactorizar cuando lo necesite. Las soluciones genéricas no proporcionan eso; desacoplan la aplicación del dominio real.

Observe los marcos ORM o MVC modernos, por ejemplo, Ruby on Rails; en el nivel de aplicación, todo el enfoque está en hacer un trabajo no genérico. Las bibliotecas de rieles en sí mismas son, obviamente, casi 100% genéricas, pero el código de dominio (sobre el que trata su pregunta) debería tener un mínimo de shenanigans en ese aspecto.

    
respondido por el AnoE 03.12.2017 - 14:11
fuente
0

Una forma diferente de pensar sobre el problema es considerar qué tiene sentido.

Por ejemplo, había una aplicación que estaba desarrollando y tenía secciones que hacían casi lo mismo pero tenían reglas de permisos inconsistentes. Como no había ninguna razón para mantenerlos diferentes, cuando reformulé esa sección, hice que todos hicieran los permisos de la misma manera. Hizo que el código general fuera más pequeño, más sencillo y que la interfaz fuera más consistente.

Cuando la administración decidió permitir que otras personas accedieran a una función, pudimos hacerlo simplemente cambiando una bandera.

Obviamente, tiene sentido hacer la conversión de tipo específica. ¿También tiene sentido hacer conversiones de tipos adicionales?

Tenga en cuenta que si la solución general es más rápida de implementar, entonces el caso específico también es sencillo, simplemente verifique que sea la única conversión de tipo que está permitiendo.

Si la aplicación se encuentra en un área altamente regulada (una aplicación médica o financiera) intente involucrar a más personas en su diseño.

    
respondido por el Robert Baron 03.12.2017 - 15:18
fuente

Lea otras preguntas en las etiquetas