Digamos que tengo una entidad que tiene el atributo "type". 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, ¿debo implementar algo que permita cambios de tipo arbitrarios siempre que sean tipos válidos? ¿O SOLO debería permitir que cambie de A-> B según el requisito y rechazar cualquier otro tipo de cambio 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 equivocarse (aunque controlamos al 100% a la persona que llama en este momento). 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, ¿qué parece una solución genérica?
Editar:
Agregar 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 tanto en el tipo antiguo como en el nuevo, mientras que la solución genérica solo necesita validar el nuevo tipo.
fuente
Respuestas:
Mi regla de oro:
Por supuesto, esta es una guía y no una regla estricta: la respuesta real es utilizar su mejor criterio, caso por caso.
fuente
thing1, thing2
, piense en usar una matriz. Enthing1, thing2, thing3
, casi con toda seguridad usething[]
matriz en su lugar". es una regla general similar para decidir entre múltiples variables o una sola matriz.He escuchado este argumento varias docenas de veces y, según mi experiencia, regularmente resulta ser una falacia. Si generaliza ahora o más tarde, cuando surja el segundo requisito similar, el trabajo será casi el mismo en total. Por lo tanto, no tiene sentido invertir un esfuerzo adicional en generalizar, cuando no sabe que este esfuerzo alguna vez dará sus frutos.
(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ó luego en la pregunta, y no es uno mi respuesta es sobre).
Cuando aparece el "segundo caso similar", es hora de comenzar a pensar en generalizar. Será mucho más fácil generalizar correctamente entonces, porque este segundo requisito le brinda un escenario en el que puede validar si hizo las cosas genéricas correctas. Cuando intentas generalizar solo para un caso, estás disparando en la oscuridad. Hay muchas posibilidades de que generalice 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 generalizaste las cosas equivocadas, entonces tienes mucho más trabajo por hacer para solucionarlo.
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 se perdió la ocasión de generalizar tres, cuatro o más veces y luego tener una pila de código de aspecto similar (tan duplicado) para mantener.
fuente
TL; DR: depende de lo que intentes resolver.
Tuve una conversación similar con mis abuelos sobre esto, mientras estábamos hablando de 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 ocuparon toda una habitación.
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 un hobby. Aprendí a programar con él, sentándome en su regazo mientras no era más que un devling, quitando mis primeras líneas de código en el editor azul de SideKick de IBM. Cuando tenía 20 años, ya había pasado más tiempo codificando que jugando afuera.
Esos son algunos de mis recuerdos, así que discúlpeme si no soy exactamente práctico mientras los vuelvo a contar. Soy un poco aficionado a esos momentos.
Eso es lo que me dijo:
"¿Deberíamos buscar la generalización de un problema o resolverlo en el ámbito específico, pregunta? Bueno, esa es una ... pregunta".
Gramps hizo una pausa para pensarlo por un breve momento, mientras fijaba la posición de sus lentes en su rostro. Estaba jugando un juego de combinar 3 en su computadora, mientras escuchaba un LP de Deep Purple en su antiguo sistema de sonido.
"Bueno, eso dependería de qué problema estés tratando de resolver", me dijo. "Es tentador creer que existe una única solución sagrada para todas las opciones de diseño, pero no hay una. La arquitectura de software es como el queso".
"... Queso, abuelo?"
"No importa lo que pienses sobre tu favorito, siempre habrá alguien que piense que es maloliente".
Parpadeé confundido por un momento, pero antes de que pudiera decir algo, Gramps continuó.
"Cuando estás construyendo un auto, ¿cómo eliges el material para una parte?"
"Yo ... supongo que depende de los costos involucrados y de lo que debe hacer la parte, supongo".
"Depende del problema que la parte esté tratando de resolver. No harás una llanta de acero o un parabrisas de cuero. Escoges el material que mejor resuelva el problema que tienes a mano. Ahora, ¿qué es un ¿Solución genérica? ¿O específica? ¿Para qué problema, para qué caso de uso? ¿Debería adoptar un enfoque funcional completo para dar la máxima flexibilidad a un código que se utilizará solo una vez? ¿Debería escribir un código muy especializado y frágil para una parte de su sistema que verá muchos usos y posiblemente muchos cambios? Las opciones de diseño como esas son los materiales que elige para una parte de un automóvil o la forma del ladrillo de Lego que elige para construir una pequeña casa ¿Qué ladrillo de Lego es el mejor?
El programador anciano buscó un pequeño modelo de tren de Lego que tiene sobre su mesa antes de continuar.
"Solo puedes responder eso si sabes para qué necesitas ese ladrillo. Cómo demonios sabrás si la solución específica es mejor que la genérica, o viceversa, si ni siquiera sabes qué problema tienes tratando de resolver? No puedes ver más allá de una elección que no entiendes ".
"... ¿Acabas de citar The Matrix? "
"¿Qué?"
"Nada, continúa".
"Bueno, supongamos que estás tratando de construir algo para el Sistema Nacional de Facturas. Sabes cómo se vería esa API infernal y su archivo XML de treinta mil líneas desde adentro. ¿Cómo se vería una solución 'genérica' para crear ese archivo? el archivo está lleno de parámetros opcionales, lleno de casos que solo las ramas de negocios específicas deberían usar. En la mayoría de los casos, puede ignorarlos de manera segura. No es necesario crear un sistema de factura genérico si lo único que usted necesita es " alguna vez venderé zapatos. Simplemente cree un sistema para vender zapatos y conviértalo en el mejor sistema de facturación de ventas de zapatos. Ahora, si tuviera que crear un sistema de facturación para cualquier tipo de cliente, en una aplicación más amplia: para ser revendido como un sistema de ventas independiente y genérico,por ejemplo, ahora es interesante implementar esas opciones que solo se usan para gas, alimentos o alcohol.Ahora esos son posibles casos de uso. Antes eran solo algunos casos hipotéticos de No usar , y no desea implementar No usar casos. No usar es el hermano pequeño de No necesito ".
Gramps volvió a colocar el tren de lego en su lugar y volvió a su juego de combinar 3.
"Por lo tanto, para poder elegir una solución genérica o específica para un problema dado, primero debes comprender cuál es ese problema. De lo contrario, solo estás adivinando, y adivinar es el trabajo de los gerentes, no de los programadores. Como casi todo en TI, depende ".
Entonces, ahí lo tienes. "Depende". Esa es probablemente la expresión de dos palabras más poderosa cuando se piensa en el diseño de software.
fuente
Principalmente, debe tratar de anticipar si es probable que ocurra tal cambio, no solo una posibilidad remota en algún momento.
Si no, generalmente es mejor optar por la solución simple ahora y extenderla más tarde. Es muy posible que tenga una imagen mucho más clara de lo que se necesita entonces.
fuente
Si está trabajando en un dominio que es nuevo para usted, entonces la Regla de tres que Daniel Pryden mencionó definitivamente debería aplicarse. Después de todo, ¿cómo se supone que construirás abstracciones útiles si eres un novato en esa área? Las personas a menudo están seguras de sí mismas en su capacidad de 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 incorrectas son realmente dolorosas de comprender. A veces aún más doloroso refactorizar.
Hay un libro que aborda mi punto acerca de un área desconocida en la que el desarrollador está trabajando. Consiste en dominios específicos con extracciones útiles extraídas.
fuente
Dada la naturaleza del cuerpo de su pregunta, suponiendo que la haya entendido correctamente, en realidad 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 errar por el lado del minimalismo, especialmente teniendo en cuenta que, en general, es más fácil agregar funcionalidades centralizadas, deseadas desde hace mucho tiempo, que eliminar funciones problemáticas, indeseadas durante mucho tiempo, con numerosas dependencias porque ha hecho que trabajar con el sistema sea mucho más difícil de lo que debe ser al tiempo que plantea interminables preguntas de diseño con cada nueva característica.
De hecho, a falta de 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 solo buscarlo como una forma de transformar el estado de A. Por ejemplo, configure algunos campos en A para que se transforme y parezca B para el usuario sin cambiar realmente a un 'tipo' de cosas diferente: podría hacer que A almacene B de forma privada utilizando la composición y las funciones de llamada en B cuando el estado de A está configurado para indicar que debe imitar 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, haciendo eco de muchos otros, sugeriría errar por el lado de evitar soluciones genéricas en este caso, pero más aún porque supongo que esto es en consideración de agregar una capacidad muy audaz al sistema central, y allí me gustaría sugiera errar al lado de dejarlo afuera, especialmente por ahora.
fuente
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 esta razón, muchos programas de juegos usan el patrón de componente de entidad en lugar de construir un sistema de tipo muy elaborado pero rígido de los personajes y objetos en el juego.
Por otro lado, hacer algo genérico requiere una inversión inicial de tiempo y esfuerzo en diseño que es mucho mayor que para algo muy específico. Esto conlleva el riesgo de una ingeniería excesiva e incluso de perderse en posibles requisitos futuros.
Siempre vale la pena ver si hay una generalización natural que lo lleve a un gran avance. Sin embargo, al final, es una cuestión de equilibrio, entre el esfuerzo que puede gastar ahora y el esfuerzo que pueda necesitar en el futuro.
fuente
Híbrido. Esto no tiene por qué ser una o una pregunta. Puede diseñar la API para conversiones de tipo general mientras implementa solo la conversión específica que necesita en este momento. (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").
Pruebas. Para la conversión A-> B, voy a tener que escribir una (o una pequeña cantidad) de pruebas. Para una conversión genérica x-> y, podría tener que escribir una matriz completa de pruebas. Eso es mucho más trabajo, incluso si todas las conversiones comparten una única 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 podría inclinarme por una solución genérica antes.
Acoplamiento. Un convertidor de A a B necesariamente necesita conocer los detalles de implementación sobre A y B (acoplamiento estrecho). Si A y B todavía están evolucionando, esto significa que podría tener que seguir revisando 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 evolucionan las C y las D, es posible que deba seguir ajustando el convertidor genérico (y un montón de pruebas), lo que puede retrasarme incluso aunque nadie todavía tiene que convertir en una C o una D .
Si tanto las conversiones genéricas como las específicas se pueden implementar de una manera que solo esté unida a los detalles de los tipos, entonces no me preocuparía por esto. Si una de ellas se puede hacer de una manera flojamente acoplada pero la otra requiere un acoplamiento estrecho, entonces ese es un fuerte argumento para el enfoque flojo de la puerta.
fuente
Esa no es una pregunta que responda.
Lo mejor que puede obtener razonablemente es algunas heurísticas para decidir qué tan general o específico es hacer una solución dada. Trabajando en algo como el proceso a continuación, generalmente la aproximación de primer orden es correcta (o lo suficientemente buena). Cuando no lo es, es probable que el motivo sea demasiado específico del dominio para ser cubierto de manera útil en detalle aquí.
Aproximación de primer orden: la regla habitual de tres de YAGNI según lo descrito por Daniel Pryden, Doc Brown, et al .
Esta es una heurística generalmente útil porque es probablemente lo mejor que puede hacer sin depender del dominio y otras variables.
Entonces, la presunción inicial es: hacemos lo más específico.
Aproximación de segundo orden: según su conocimiento experto del dominio de la solución , usted dice
entonces podríamos reinterpretar YAGNI como una recomendación de evitar el trabajo innecesario , en lugar de evitar generalidades innecesarias. Entonces, podríamos modificar nuestra presunción inicial y, en cambio, 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 puede abrir muchos errores, o ser difícil de probar adecuadamente, o causar cualquier otro problema, entonces ser más fácil de codificar no es necesariamente una razón suficiente para cambiar nuestro Elección original.
Aproximación de tercer orden: ¿su conocimiento del dominio del problema sugiere que la solución más fácil es realmente correcta, o está permitiendo que muchas transiciones que usted sabe no tengan sentido o estén mal?
Si la solución fácil pero genérica parece problemática, o no está seguro de su capacidad para juzgar estos riesgos, probablemente sea mejor hacer el trabajo extra y mantenerse con su suposición inicial.
Aproximación de cuarto orden: ¿su conocimiento del comportamiento del cliente, o cómo esta característica se relaciona con los demás, o las prioridades de gestión del proyecto, o ... alguna otra consideración no estrictamente técnica modifica su decisión de trabajo actual?
fuente
Esta no es una pregunta fácil de responder con una respuesta simple. Muchas respuestas han dado heurística construida alrededor de una regla de 3, o algo similar. Ir más allá de esas reglas generales es difícil.
Para responder realmente a su pregunta, debe tener en cuenta que es probable 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, lo contrataron 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 usar eso para interpretar la pequeña solicitud que se le ha dado para cambiar A-> B.
Si usted es un programador de nivel bajo recién salido de la universidad, a menudo es aconsejable hacer exactamente lo que le dijeron que hiciera. Si fue contratado como arquitecto de software con 15 años de experiencia, generalmente es aconsejable pensar en cosas importantes. Cada trabajo real va a caer en algún lugar entre "hacer exactamente lo que es la tarea estrecha" y "pensar en el panorama general". Sentirás dónde encaja tu trabajo en ese espectro si hablas con la gente lo suficiente y haces suficiente trabajo para ellos.
Puedo dar algunos ejemplos concretos donde 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 prueba de pie para garantizar que el producto funcione según lo prometido. Se requiere que algunos de estos equipos de prueba prueben cada ruta posible a través del código. Si habla con ellos, puede descubrir 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 agregue funcionalidad generalizada, incluso si tiene que duplicar el trabajo 7 u 8 veces debido a ello. Ahorre dinero a la empresa y haga exactamente lo que se indica en la solicitud.
Por otro lado, considere que está haciendo una API para permitir a los clientes acceder a los datos en un programa de base de datos que hace su empresa. Un cliente solicita permitir cambios A-> B. Las API generalmente tienen un aspecto de esposas doradas: una vez que agrega funcionalidad a una API, normalmente no se supone que elimine esa funcionalidad (hasta el próximo número de versión principal). Es posible que muchos de sus clientes no estén dispuestos a pagar el costo de actualizar al siguiente número de versión principal, por lo que puede quedarse atrapado con la solución que elija durante mucho tiempo. En este caso, yo altamente recomiendo crear la solución genérica desde el principio. Realmente no desea desarrollar una API incorrecta llena de comportamientos únicos.
fuente
Hmm ... no hay mucho contexto para obtener una respuesta ... haciéndose eco de las 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 lo que dicen los criterios de aceptación. Si se trata de algo así como "el usuario debería poder cambiar el tipo de" A "a" B "'versus' el usuario debería poder cambiar el tipo de su valor actual a cualquier valor alternativo permitido '.
Con frecuencia, los criterios de aceptación están sujetos a interpretación, pero un buen personal de control de calidad puede escribir criterios apropiados 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, sino solo "A" a "B"? ¿O es solo un requisito estrictamente especificado que no es "pensar hacia adelante"?
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 vendrían otras solicitudes de cambio de 'tipo' en el futuro, estaría tentado a: a) escriba algo reutilizable para el caso general, y b) envuélvalo en un condicional que solo permita A -> B por ahora.
Suficientemente fácil de verificar para el caso actual en pruebas automatizadas, y lo suficientemente fácil de abrirse a otras opciones más adelante si / cuando surgen diferentes casos de uso.
fuente
Para mí, una directriz 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 código real para estos ahora, solo piense un poco en lo que haría. De lo contrario, generalmente hará las cosas innecesariamente complejas y probablemente se molestará más tarde cuando surjan requisitos reales que difieran de lo que esperaba.
Cuatro su ejemplo: si tiene todos los usos del método de conversión bajo su control, puede llamarlo convertAToB por ahora y planear usar una refactorización de "método de cambio de nombre" en el IDE para cambiarle el nombre si necesita una funcionalidad más general más adelante. 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.
fuente
En principio sí. Pero esto no necesariamente conduce a soluciones genéricas.
En lo que a mí respecta, existen dos tipos de temas en el desarrollo de software, en los que debe anticipar cambios futuros:
El primer caso se resuelve observando su cohesión / acoplamiento, inyección de dependencia o lo que sea. El segundo caso está en un nivel más abstracto, por ejemplo, elegir una arquitectura orientada a servicios en lugar de una gran cantidad de código monolótico para una aplicación grande.
En su caso, está solicitando una solución específica para un problema específico, que no tenga ningún impacto previsible en el futuro. En este caso, YAGNI y DRY son buenos lemas para disparar:
En combinación con otras prácticas modernas (como una buena cobertura de prueba para poder refactorizar de manera segura), esto significa que terminará con un código medio rápido, simple y escrito que crece según sea necesario.
No, parece que debería tener entornos de programación, lenguajes y herramientas que faciliten y refactoricen cuando lo necesiten. Las soluciones genéricas no proporcionan eso; desacoplan la aplicación del dominio real.
Eche un vistazo a los marcos ORM o MVC modernos, por ejemplo, Ruby on Rails; a nivel de aplicación, todo se centra en hacer un trabajo no genérico. Obviamente, las bibliotecas de rails son casi 100% genéricas, pero el código de dominio (del que trata su pregunta) debería estar haciendo travesuras mínimas en ese aspecto.
fuente
Una forma diferente de pensar sobre el problema es considerar lo que tiene sentido.
Por ejemplo, había una aplicación que estaba desarrollando que tenía secciones que hacían casi lo mismo pero tenían reglas de permiso inconsistentes. Como no había razón para mantenerlos diferentes, cuando reescribí esa sección, hice que todos hicieran los permisos de la misma manera. Esto hizo que el código general fuera más pequeño, más simple y la interfaz era más consistente.
Cuando la gerencia decidió permitir el acceso de otras personas a una función, pudimos hacerlo simplemente cambiando una bandera.
Obviamente tiene sentido hacer la conversión de tipo específico. ¿También tiene sentido hacer conversiones de tipo 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 fácil, solo 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.
fuente