design - SÓLIDO vs. YAGNI
oop solid-principles (10)
Comprensible, flexible y capaz de corregir y mejorar son siempre cosas que va a necesitar. De hecho, YAGNI asume que puede volver y agregar nuevas funciones cuando sea necesario con relativa facilidad, porque nadie va a hacer algo loco como tapar funcionalidad irrelevante en una clase (¡YAGNI en esa clase!) O empujar la lógica empresarial a la lógica de UI .
Puede haber momentos en los que lo que parece loco ahora era razonable en el pasado: a veces las líneas fronterizas de UI versus negocio o entre diferentes conjuntos de responsabilidades que deberían estar en una clase diferente no son tan claras, o incluso se mueven. Puede haber momentos en que 3 horas de trabajo sean absolutamente necesarias en 2 horas. Hay momentos en que las personas simplemente no hacen la llamada correcta. Por esas razones, ocasionalmente se producirán interrupciones en este sentido, pero van a interferir en el uso del principio YAGNI, no ser una causa de ello.
Uno de los argumentos más frecuentes que escucho para no adherirme a los principios SOLID en el diseño orientado a objetos es YAGNI (aunque el argumentador a menudo no lo llama así):
"Está bien que coloque la característica X y la característica Y en la misma clase. Es tan simple por qué molestarse en agregar una nueva clase (es decir, complejidad)".
"Sí, puedo poner toda mi lógica de negocios directamente en el código de la GUI, es mucho más fácil y más rápido. Esta siempre será la única GUI y es muy poco probable que lleguen nuevos requisitos significativos".
"Si en el caso improbable de nuevos requisitos mi código se llena demasiado, aún puedo refactorizar para el nuevo requisito. Por lo tanto, su argumento ''Qué pasaría si usted necesita ...'' no cuenta".
¿Cuáles serían sus argumentos más convincentes contra tal práctica? ¿Cómo puedo demostrar realmente que esta es una práctica costosa, especialmente para alguien que no tiene demasiada experiencia en desarrollo de software?
En mi experiencia, siempre es un juicio crítico. Sí, no debe preocuparse por cada pequeño detalle de su implementación y, a veces, incluir un método en una clase existente es una solución aceptable, aunque fea.
Es cierto que puedes refactorizar más tarde. El punto importante es hacer la refactorización . Así que creo que el verdadero problema no es el compromiso de diseño ocasional, sino posponer la refactorización una vez que queda claro que hay un problema. En realidad, seguir con esto es la parte difícil (al igual que con muchas cosas en la vida ...).
En cuanto a sus puntos individuales:
Está bien que coloque la característica X y la característica Y en la misma clase. Es tan simple por qué molestarse en agregar una nueva clase (es decir, complejidad).
Me gustaría señalar que tener todo en una clase es más complejo (porque la relación entre los métodos es más íntima y más difícil de entender). Tener muchas clases pequeñas no es complejo. Si crees que la lista es larga, simplemente organízalos en paquetes y estarás bien :-). Personalmente, he descubierto que simplemente dividir una clase en dos o tres clases puede ayudar mucho con la legibilidad, sin más cambios.
No tengas miedo de las clases pequeñas, no muerden ;-).
Sí, puedo poner toda mi lógica comercial directamente en el código de la GUI, es mucho más fácil y rápido. Esta siempre será la única GUI y es muy poco probable que surjan nuevos requisitos significativos.
Si alguien puede decir "es muy poco probable que nuevos requisitos significativos entren nunca". con una cara seria, creo que esa persona realmente, realmente necesita un control de la realidad. Sé franco, pero gentil ...
Si en el caso improbable de nuevos requisitos mi código se llena demasiado, todavía puedo refactorizar para el nuevo requisito. Por lo tanto, su argumento ''Qué pasaría si luego necesita ...'' no cuenta
Eso tiene algún mérito, pero solo si realmente se refactorizan más tarde. Así que acéptalo y mantenlo firme en su promesa :-).
La correcta aplicación de estos principios a menudo no es muy obvia y depende mucho de la experiencia. Lo cual es difícil de obtener si no lo hiciste tú mismo. Cada programador debería haber tenido experiencias de las consecuencias de hacerlo mal, pero por supuesto siempre debería ser "no es mi" proyecto.
Explíqueles cuál es el problema, si no escuchan y no están en condiciones de obligarlos a escuchar, permítales cometer los errores. Si con demasiada frecuencia tiene que solucionar el problema, debe pulir su currículum.
Las pruebas de unidad de calidad, y me refiero a las pruebas unitarias, no a las pruebas de integración, necesitan un código que se adhiera a SOLID. No necesariamente 100%, de hecho rara vez, pero en su ejemplo incluir dos características en una clase dificultará las pruebas unitarias, romperá el principio de responsabilidad única y hará que el mantenimiento de código por parte de los novatos sea mucho más difícil (ya que es mucho más difícil de comprender) .
Con las pruebas unitarias (suponiendo una buena cobertura de código) podrá refactorizar la característica 1 de manera segura, no romperá la función 2, pero sin pruebas de unidad y con las características en la misma clase (simplemente para ser flojo en su ejemplo) refactorizar es arriesgado en el mejor de los casos, desastroso en el mejor de los casos.
En pocas palabras: siga el principio KIS (mantenerlo simple), o para el principio intelectual KISS (es estúpido). Considere cada caso por mérito, no hay una respuesta global, pero siempre considere si otros codificadores necesitan leer / mantener el código en el futuro y el beneficio de las pruebas unitarias en cada escenario.
Los principios SOLID permiten que el software se adapte al cambio, tanto en requisitos como en cambios técnicos (nuevos componentes, etc.), dos de sus argumentos son para requisitos invariables:
- "es muy poco probable que nuevos requisitos significativos lleguen".
- "Si en el caso improbable de nuevos requisitos"
¿Puede ser esto realmente cierto?
No hay sustituto para la experiencia cuando se trata de los diversos gastos del desarrollo. Para muchos practicantes, creo que hacer cosas de la manera difícil y difícil de mantener nunca les ha ocasionado problemas (¡seguridad en el trabajo!). A largo plazo de un producto, creo que estos gastos se aclaran, pero hacer algo al respecto con anticipación es el trabajo de otra persona.
Hay algunas otras respuestas excelentes aquí.
Me gusta pensar en YAGNI en términos de "la mitad, no a medias", para tomar prestada la frase de 37signals ( https://gettingreal.37signals.com/ch05_Half_Not_Half_Assed.php ). Se trata de limitar su alcance para que pueda concentrarse en hacer bien las cosas más importantes. No es una excusa para descuidarse.
La lógica empresarial en la GUI se siente medio asediada. A menos que su sistema sea trivial, me sorprendería si su lógica de negocios y su GUI no han cambiado de forma independiente varias veces. Por lo tanto, debe seguir el SRP ("S" en SOLID) y el refactor: YAGNI no se aplica, porque ya lo necesita.
El argumento sobre YAGNI y la complejidad innecesaria se aplica absolutamente si está haciendo un trabajo extra hoy para acomodarse a requisitos futuros hipotéticos. Cuando esos escenarios "¿y si más adelante necesitamos ..." no se materializan, usted está atascado con los costos de mantenimiento más altos de las abstracciones que ahora se interponen en el camino de los cambios que realmente tiene. En este caso, estamos hablando de simplificar el diseño al limitar el alcance, hacer la mitad, en lugar de estar medio dormido.
No hay respuesta, o más bien, hay una respuesta que ni a usted ni a su interlocutor le puede gustar: tanto YAGNI como SOLID pueden ser enfoques equivocados.
Intentar ir a SOLID con un equipo inexperto, o un equipo con objetivos de entrega ajustados, prácticamente garantiza que terminará con un montón de códigos costosos y over-engineered ... que NO SERÁN SÓLIDOS, simplemente sobreingenidos (también bienvenidos) al mundo real).
Intentar ir a YAGNI para un proyecto a largo plazo y esperar que puedas refactorizar más tarde solo funciona hasta cierto punto (también conocido como Bienvenido al mundo real). YAGNI sobresale en la prueba de conceptos y demostradores, obtener el mercado / contrato y luego poder invertir en algo más SÓLIDO.
Necesitas ambos, en diferentes momentos.
Parece que estás discutiendo con una pared de ladrillos. Soy un gran admirador de YAGNI, pero al mismo tiempo, también espero que mi código siempre se use en al menos dos lugares: la aplicación y las pruebas. Es por eso que las cosas como la lógica de negocios en el código de la interfaz de usuario no funcionan; no puede probar la lógica de negocios separada del código de UI en esa circunstancia.
Sin embargo, de las respuestas que describes, parece que la persona simplemente no está interesada en hacer un mejor trabajo. En ese punto, ningún principio los va a ayudar; solo quieren hacer lo mínimo posible. Iría tan lejos como para decir que no es YAGNI conduciendo sus acciones, sino más bien pereza, y usted solo no va a vencer a la pereza (casi nada puede, excepto un gerente amenazante o la pérdida de un trabajo).
tldr;
SOLID asume, usted entiende (al menos por lo menos), los cambios futuros en el código, wrt SRP. Diré que es optimista sobre la capacidad de predecir. YAGNI, por otro lado, asume la mayoría de las veces que usted no sabe la dirección futura del cambio, que es pesimista sobre la capacidad de predecir.
Por lo tanto, se deduce que SOLID / SRP le pide que forme clases para el código de modo que tenga una sola razón para el cambio. Por ejemplo, un pequeño cambio de GUI o un cambio de ServiceCall.
YAGNI dice (si quieres forzar aplicarlo en este escenario), ya que no sabes QUÉ va a cambiar, y si un cambio en la GUI causará un cambio en la GUI + Llamada de Servicio (similarmente un cambio de back-end que causa cambio de GUI + SeviceCall) , simplemente pon todo ese código en una sola clase.
Respuesta larga :
Lea el libro ''Desarrollo de software ágil, principios, patrones y prácticas''
Voy a resumir brevemente sobre SOLID / SRP: "Si [...] la aplicación no cambia de manera tal que las dos responsabilidades cambien en diferentes momentos, no hay necesidad de separarlas. De hecho, separándolas. olería a complejidad innecesaria.
Hay un corrolario aquí. Un eje de cambio es un eje de cambio solo si ocurren los cambios. No es prudente aplicar SRP, o cualquier otro principio, para el caso, si no hay ningún síntoma ".
El diseño es la gestión y el equilibrio de las compensaciones. YAGNI y SOLID no son contradictorios: el primero dice cuándo agregar características, el último dice cómo, pero ambos guían el proceso de diseño. Mis respuestas, a continuación, a cada una de sus citas específicas usan principios de YAGNI y SOLID.
- Es tres veces más difícil construir componentes reutilizables como componentes de un solo uso.
- Un componente reutilizable se debe probar en tres aplicaciones diferentes antes de que sea lo suficientemente general como para aceptarlo en una biblioteca de reutilización.
- Robert Glass '' Reglas de tres , hechos y falacias de la ingeniería de software
Refactorizar en componentes reutilizables tiene el elemento clave de encontrar primero el mismo propósito en varios lugares y luego moverlo. En este contexto, YAGNI aplica haciendo hincapié en ese propósito donde sea necesario, sin preocuparse por una posible duplicación, en lugar de agregar características genéricas o reutilizables (clases y funciones).
La mejor manera, en el diseño inicial, de mostrar cuándo YAGNI no se aplica es identificar requisitos concretos. En otras palabras, haga algo de refactorización antes de escribir el código para mostrar que la duplicación no es meramente posible, sino que ya existe: esto justifica el esfuerzo adicional.
Sí, puedo poner toda mi lógica comercial directamente en el código de la GUI, es mucho más fácil y rápido. Esta siempre será la única GUI y es muy poco probable que surjan nuevos requisitos significativos.
¿Es realmente la única interfaz de usuario? ¿Hay un modo por lotes de fondo planificado? ¿Alguna vez habrá una interfaz web?
¿Cuál es su plan de prueba y va a probar la funcionalidad del back-end sin una GUI? Lo que hará que la GUI sea fácil de probar, ya que generalmente no desea probar código externo (como los controles de GUI genéricos de la plataforma) y, en cambio, concentrarse en su proyecto.
Está bien que coloque la característica X y la característica Y en la misma clase. Es tan simple por qué molestarse en agregar una nueva clase (es decir, complejidad).
¿Puedes señalar un error común que debe evitarse? Algunas cosas son lo suficientemente simples, como cuadrar un número ( x * x
vs squared(x)
) para un ejemplo demasiado simple, pero si puede señalar un error concreto que alguien cometió, especialmente en su proyecto o por parte de su equipo -Puede mostrar cómo una clase o función común evitará eso en el futuro.
Si, en el caso poco probable de nuevos requisitos, mi código se llena demasiado, aún puedo refactorizar para el nuevo requisito. Por lo tanto, su argumento "Qué pasa si más adelante tiene que ..." no cuenta.
El problema aquí es la suposición de "improbable". ¿Estás de acuerdo en que es poco probable? Si es así, estás de acuerdo con esta persona. De lo contrario, su idea del diseño no concuerda con la de esta persona: resolver esa discrepancia resolverá el problema o, al menos, le indicará dónde ir. :)