Soy desarrollador junior entre personas mayores y estoy luchando mucho para comprender su pensamiento y razonamiento.
Estoy leyendo Diseño controlado por dominio (DDD) y no puedo entender por qué necesitamos crear tantas clases. Si seguimos ese método de diseño de software, terminamos con 20-30 clases que pueden reemplazarse como máximo con dos archivos y 3-4 funciones. Sí, esto podría ser complicado, pero es mucho más fácil de mantener y leer.
Cada vez que quiero ver qué hace algún tipo EntityTransformationServiceImpl
, necesito seguir muchas clases, interfaces, sus llamadas a funciones, constructores, su creación, etc.
Matemática simple:
- 60 líneas de código ficticio frente a 10 clases X 10 (supongamos que tenemos lógicas totalmente diferentes) = 600 líneas de código desordenado frente a 100 clases + algunas más para envolverlas y administrarlas; No olvides agregar la inyección de dependencia.
- Lectura de 600 líneas de código desordenado = un día
- 100 clases = una semana, todavía olvida cuál hace qué, cuándo
Todos dicen que es fácil de mantener, pero ¿para qué? Cada vez que agrega una nueva funcionalidad, agrega cinco clases más con fábricas, entidades, servicios y valores. Siento que este tipo de código se mueve mucho más lento que el código desordenado.
Digamos que si escribe un código desordenado de 50K LOC en un mes, la cosa DDD requiere muchas revisiones y cambios (no me importan las pruebas en ambos casos). Una simple adición puede llevar una semana, si no más.
En un año, escribe un montón de código desordenado e incluso puede reescribirlo varias veces, pero con el estilo DDD, aún no tiene suficientes funciones para competir con el código desordenado.
Por favor explique. ¿Por qué necesitamos este estilo DDD y muchos patrones?
UPD 1 : Recibí muchas respuestas geniales, ¿pueden agregar algún comentario o editar su respuesta con el enlace para leer la lista (no estoy seguro de dónde comenzar, DDD, Patrones de diseño, UML, Código completo, Refactorización, Pragmática, etc.). .. tantos buenos libros), por supuesto con secuencia, para que yo también pueda comenzar a entender y llegar a ser mayor como algunos de ustedes.
fuente
Respuestas:
Este es un problema de optimización
Un buen ingeniero entiende que un problema de optimización no tiene sentido sin un objetivo. No puedes simplemente optimizar, tienes que optimizar para algo. Por ejemplo, las opciones del compilador incluyen la optimización de la velocidad y la optimización del tamaño del código; Estos son a veces objetivos opuestos.
Me gusta decirle a mi esposa que mi escritorio está optimizado para agregar anuncios. Es solo una pila, y es muy fácil agregar cosas. Mi esposa preferiría que yo optimizara la recuperación, es decir, organizara mis cosas un poco para poder encontrar cosas. Esto hace que sea más difícil, por supuesto, agregar.
El software es de la misma manera. Sin duda, puede optimizar para la creación de productos: generar una tonelada de código monolítico lo más rápido posible, sin preocuparse por organizarlo. Como ya has notado, esto puede ser muy, muy rápido. La alternativa es optimizar el mantenimiento: hacer que la creación sea un poco más difícil, pero hacer que las modificaciones sean más fáciles o menos riesgosas. Ese es el propósito del código estructurado.
Sugeriría que un producto de software exitoso solo se creará una vez, pero se modificará muchas, muchas veces. Los ingenieros experimentados han visto cómo las bases de código no estructuradas cobran vida propia y se convierten en productos, creciendo en tamaño y complejidad, hasta que incluso los pequeños cambios son muy difíciles de realizar sin introducir un gran riesgo. Si el código fuera estructurado, el riesgo puede ser contenido. Por eso nos vamos a todos estos problemas.
La complejidad proviene de las relaciones, no de los elementos.
Noto en su análisis que está buscando cantidades: cantidad de código, número de clases, etc. Si bien estos son algo interesantes, el impacto real proviene de las relaciones entre elementos, que explotan combinatoriamente. Por ejemplo, si tiene 10 funciones y no tiene idea de qué depende, tiene 90 posibles relaciones (dependencias) de las que debe preocuparse: cada una de las diez funciones puede depender de cualquiera de las otras nueve funciones, y 9 x 10 = 90. Es posible que no tenga idea de qué funciones modifican qué variables o cómo se transmiten los datos, por lo que los codificadores tienen un montón de cosas de qué preocuparse al resolver cualquier problema en particular. Por el contrario, si tiene 30 clases pero están organizadas de manera inteligente, pueden tener tan solo 29 relaciones, por ejemplo, si están en capas o dispuestas en una pila.
¿Cómo afecta esto el rendimiento de su equipo? Bueno, hay menos dependencias, el problema es mucho más manejable; los programadores no tienen que hacer malabarismos con un millón de cosas en su cabeza cada vez que hacen un cambio. Por lo tanto, minimizar las dependencias puede ser un gran impulso para su capacidad de razonar sobre un problema de manera competente. Es por eso que dividimos las cosas en clases o módulos, y las variables de alcance lo más estrictamente posible, y utilizamos principios SOLIDOS .
fuente
EntityTransformationServiceImpl
, se ve obligado a aprender cómo funciona todo antes de poder solucionarlo, pero si, por ejemplo, algo está mal con el formato y unaFormatter
clase lo maneja, solo necesita aprender cómo funciona esa parte . Además leer su propio código después de ~ 3 meses es como leer el código de un extraño.) Siento que la mayoría de nosotros inconscientemente pensamos en esto, pero eso podría deberse a la experiencia. No estoy 100% seguro de esto.Bueno, en primer lugar, la legibilidad y la facilidad de mantenimiento suelen estar en el ojo del espectador.
Lo que es legible para usted puede no serlo para su vecino.
La mantenibilidad a menudo se reduce a la capacidad de descubrimiento (con qué facilidad se descubre un comportamiento o concepto en la base de código) y la capacidad de descubrimiento es otra cosa subjetiva.
DDD
Una de las formas en que DDD ayuda a los equipos de desarrolladores es sugiriendo una forma específica (pero aún subjetiva) de organizar los conceptos y comportamientos de su código. Esta convención hace que sea más fácil descubrir cosas y, por lo tanto, más fácil de mantener la aplicación.
Esta disposición no es objetivamente más fácil de mantener. Es, sin embargo, mensurable más fácil de mantener cuando todo el mundo entiende que están operando en un contexto DDD.
Clases
Las clases ayudan con el mantenimiento, la legibilidad, la capacidad de descubrimiento, etc. porque son una convención bien conocida .
En la configuración orientada a objetos, las clases se usan generalmente para agrupar comportamientos estrechamente relacionados y encapsular el estado que debe controlarse cuidadosamente.
Sé que suena muy abstracto, pero puedes pensarlo de esta manera:
Con las clases, no necesariamente necesita saber cómo funciona el código en ellas. Solo necesita saber de qué es responsable la clase.
Las clases le permiten razonar sobre su aplicación en términos de interacciones entre componentes bien definidos .
Esto reduce la carga cognitiva al razonar sobre cómo funciona su aplicación. En lugar de tener que recordar lo que logran 600 líneas de código , puede pensar en cómo interactúan 30 componentes .
Y, teniendo en cuenta que esos 30 componentes probablemente abarcan 3 capas de su aplicación, probablemente solo tenga que razonar aproximadamente 10 componentes a la vez.
Eso parece bastante manejable.
Resumen
Esencialmente, lo que estás viendo hacer a los desarrolladores senior es esto:
Están desglosando la aplicación en fácil de razonar sobre las clases.
Luego los organizan en capas fáciles de razonar .
Lo están haciendo porque saben que, a medida que la aplicación crece, cada vez es más difícil razonar sobre ello en su conjunto. Desglosarlo en capas y clases significa que nunca tienen que razonar sobre la aplicación completa. Solo necesitan razonar sobre un pequeño subconjunto.
fuente
AddressServiceRepositoryProxyInjectorResolver
cuando puedes resolver el problema con más elegancia? Los patrones de diseño en aras de los patrones de diseño solo conducen a una complejidad y verbosidad innecesarias.Primero, una nota: la parte importante de DDD no son los patrones , sino la alineación del esfuerzo de desarrollo con el negocio. Greg Young comentó que los capítulos del libro azul están en el orden incorrecto .
Pero a su pregunta específica: tiende a haber muchas más clases de las que cabría esperar, (a) porque se está haciendo un esfuerzo para distinguir el comportamiento del dominio de la tubería y (b) porque se está haciendo un esfuerzo adicional para garantizar que los conceptos en el modelo de dominio se expresan explícitamente.
Sin rodeos, si tiene dos conceptos diferentes en el dominio, entonces deben ser distintos en el modelo, incluso si comparten la misma representación de memoria .
En efecto, está creando un lenguaje específico de dominio que describe su modelo en el idioma de la empresa, de modo que un experto en dominios debería poder verlo y detectar errores.
Además, observa que se presta un poco más de atención a la separación de preocupaciones; y la noción de aislar a los consumidores de alguna capacidad de los detalles de implementación. Ver DL Parnas . Los límites claros le permiten cambiar o ampliar la implementación sin que los efectos se extiendan por toda su solución.
La motivación aquí: para una aplicación que es parte de la competencia central de una empresa (es decir, un lugar donde obtiene una ventaja competitiva), querrá poder reemplazar de manera fácil y económica un comportamiento de dominio con una mejor variación. En efecto, tiene partes del programa que desea evolucionar rápidamente (cómo evoluciona el estado con el tiempo) y otras partes que desea cambiar lentamente (cómo se almacena el estado); Las capas adicionales de abstracción ayudan a evitar el acoplamiento inadvertido entre sí.
Para ser justos: algunos de ellos también son daños cerebrales orientados a objetos. Los patrones descritos originalmente por Evans se basan en proyectos Java en los que participó hace más de 15 años; el estado y el comportamiento están estrechamente vinculados en ese estilo, lo que conduce a complicaciones que quizás prefieras evitar; ver Percepción y acción de Stuart Halloway, o Código de línea de John Carmack.
fuente
Hay muchos puntos buenos en las otras respuestas, pero creo que fallan o no enfatizan un error conceptual importante que cometes:
Estás comparando el esfuerzo para comprender el programa completo.
Esta no es una tarea realista con la mayoría de los programas. Incluso los programas simples consisten en tanto código que es simplemente imposible administrarlo todo en la cabeza en un momento dado. Su única oportunidad es encontrar la parte de un programa que sea relevante para la tarea en cuestión (corregir un error, implementar una nueva característica) y trabajar con eso.
Si su programa consta de funciones / métodos / clases enormes, esto es casi imposible. Tendrá que comprender cientos de líneas de código solo para decidir si este fragmento de código es relevante para su problema. Con las estimaciones que dio, es fácil pasar una semana solo para encontrar el código en el que necesita trabajar.
Compare eso con una base de código con pequeñas funciones / métodos / clases nombrados y organizados en paquetes / espacios de nombres que hacen obvio dónde encontrar / poner una determinada pieza de lógica. Cuando se hace correctamente en muchos casos, puede saltar directamente al lugar correcto para resolver su problema, o al menos a un lugar desde donde encender su depurador lo llevará al lugar correcto en un par de saltos.
He trabajado en ambos tipos de sistemas. La diferencia puede ser fácilmente dos órdenes de magnitud en rendimiento para tareas comparables y tamaño de sistema comparable.
El efecto que esto tiene en otras actividades:
fuente
Porque probar código es más difícil que escribir código
Muchas respuestas han dado un buen razonamiento desde la perspectiva de un desarrollador: que el mantenimiento puede reducirse, a costa de hacer que el código sea más agotador para escribir en primer lugar.
Sin embargo, hay otro aspecto a tener en cuenta: las pruebas solo pueden definirse como su código original.
Si escribe todo en un monolito, la única prueba efectiva que puede escribir es "dadas estas entradas, ¿es correcta la salida?". Esto significa que cualquier error encontrado se encuentra en "algún lugar en ese montón gigante de código".
Por supuesto, puede hacer que un desarrollador se siente con un depurador y encuentre exactamente dónde comenzó el problema y trabaje en una solución. Esto requiere muchos recursos y es un mal uso del tiempo de un desarrollador. Imagine que cualquier error menor que tenga, hace que un desarrollador necesite depurar todo el programa nuevamente.
La solución: muchas pruebas más pequeñas que identifican una falla potencial específica cada una.
Estas pequeñas pruebas (por ejemplo, Pruebas unitarias) tienen la ventaja de verificar un área específica de la base de código y ayudar a encontrar errores dentro de un alcance limitado. Esto no solo acelera la depuración cuando falla una prueba, sino que también significa que si fallan todas sus pruebas pequeñas, puede encontrar más fácilmente la falla en sus pruebas más grandes (es decir, si no está en una función probada específica, debe estar en la interacción entre ellos).
Como debe quedar claro, hacer pruebas más pequeñas significa que su base de código debe dividirse en trozos comprobables más pequeños. La forma de hacerlo, en una gran base de código comercial, a menudo da como resultado un código similar al que está trabajando.
Solo como una nota al margen: esto no quiere decir que las personas no lleven las cosas "demasiado lejos". Pero hay una razón legítima para separar las bases de código en partes más pequeñas / menos conectadas, si se hace con sensatez.
fuente
Muchos (la mayoría ...) de nosotros realmente no los necesitamos . Los teóricos y los programadores muy avanzados y experimentados escriben libros sobre teorías y metodologías como resultado de muchas investigaciones y su profunda experiencia, eso no significa que todo lo que escriben sea aplicable a cada programador en su práctica diaria.
Como desarrollador junior, es bueno leer libros como el que mencionaste para ampliar tus perspectivas y hacerte consciente de ciertos problemas. También evitará que se sienta avergonzado y desconcertado cuando sus colegas superiores usen terminología con la que no esté familiarizado. Si encuentra algo muy difícil y no parece tener sentido o parece útil, no se suicide por eso, simplemente archívelo en la cabeza de que existe tal concepto o enfoque.
En su desarrollo diario, a menos que sea un académico, su trabajo es encontrar soluciones viables y mantenibles. Si las ideas que encuentra en un libro no lo ayudan a lograr ese objetivo, no se preocupe por eso ahora, siempre y cuando su trabajo se considere satisfactorio.
Puede llegar un momento en que descubras que puedes usar algo de lo que lees sobre lo que no "entendiste" al principio, o tal vez no.
fuente
AccountServiceFacadeInjectResolver
(ejemplo real que acabo de encontrar en un sistema en el trabajo) - la respuesta es probablemente no.Como su pregunta está cubriendo mucho terreno, con muchos supuestos, destacaré el tema de su pregunta:
Nosotros no. No existe una regla generalmente aceptada que diga que debe haber muchas clases en los patrones de diseño.
Hay dos guías clave para decidir dónde colocar el código y cómo cortar sus tareas en diferentes unidades de código:
¿Por qué deberían ser importantes esos dos?
Estos dos aspectos son los "impulsores" básicos para cualquier elección de "dónde colocar qué" en cualquier lenguaje de programación y cualquier paradigma (no solo OO). No todo el mundo los conoce explícitamente, y lleva tiempo, a menudo años, tener una sensación realmente arraigada y automática de cómo estos influyen en el software.
Obviamente, estos dos conceptos no dicen nada acerca de lo que realmente hacerlo . Algunas personas se equivocan demasiado, otras demasiado poco. Algunos lenguajes (mirándote aquí, Java) tienden a favorecer muchas clases debido a la naturaleza extremadamente estática y pedante del lenguaje en sí (esto no es una declaración de valor, pero es lo que es). Esto se vuelve especialmente notable cuando lo compara con lenguajes dinámicos y más expresivos, por ejemplo, Ruby.
Otro aspecto es que algunas personas se suscriben al enfoque ágil de escribir solo código que es necesario en este momento y refactorizar mucho más tarde, cuando sea necesario. En este estilo de desarrollo, no crearía una
interface
cuando solo tiene una clase de implementación. Simplemente implementaría la clase concreta. Si, más tarde, necesita una segunda clase, refactorizaría.Algunas personas simplemente no trabajan de esa manera. Crean interfaces (o, más generalmente, clases base abstractas) para cualquier cosa que alguna vez pueda usarse de manera más general; Esto lleva a una explosión de clase rápidamente.
Una vez más, hay argumentos a favor y en contra, y no importa cuál yo o usted prefiera. En su vida como desarrollador de software, se encontrará con todos los extremos, desde largos métodos de espagueti, pasando por diseños de clase ilustrados y lo suficientemente grandes, hasta esquemas de clase increíblemente desarrollados que están muy diseñados. A medida que tenga más experiencia, crecerá más en roles "arquitectónicos", y puede comenzar a influir en esto en la dirección que desee. Encontrarás un medio dorado para ti, y aún encontrarás que muchas personas estarán en desacuerdo contigo, hagas lo que hagas.
Por lo tanto, mantener una mente abierta es lo más importante aquí, y ese sería mi consejo principal para usted, ya que parece que le duele mucho el tema, a juzgar por el resto de su pregunta ...
fuente
Los codificadores experimentados han aprendido:
fuente
Las respuestas hasta ahora, todas son buenas, habiendo comenzado con la suposición razonable de que al autor de la pregunta le falta algo, lo que también reconoce. También es posible que el autor de la pregunta tenga razón, y vale la pena discutir cómo puede surgir esta situación.
La experiencia y la práctica son poderosas, y si los adultos mayores adquirieron su experiencia en proyectos grandes y complejos donde la única forma de mantener las cosas bajo control es con muchos
EntityTransformationServiceImpl
, entonces se vuelven rápidos y cómodos con patrones de diseño y una estrecha adhesión al DDD. Serían mucho menos efectivos utilizando un enfoque liviano, incluso para programas pequeños. Como extraño, debes adaptarte y será una gran experiencia de aprendizaje.Sin embargo, mientras se adapta, debe tomarlo como una lección en el equilibrio entre aprender un enfoque único en profundidad, hasta el punto de que puede hacer que funcione en cualquier lugar, en lugar de mantenerse versátil y saber qué herramientas están disponibles sin ser necesariamente un experto en ninguna de ellas. Hay ventajas para ambos y ambos son necesarios en el mundo.
fuente
Hacer más clases y funciones según el uso será un gran enfoque para resolver problemas de una manera específica que ayudará en el futuro a resolver cualquier problema.
Varias clases ayudan a identificarse como su trabajo básico y se puede llamar a cualquier clase en cualquier momento.
Sin embargo, si tiene muchas clases y funciones por su nombre getable, es fácil llamarlas y administrarlas. Esto se llama un código limpio.
fuente