¿Por qué necesitamos tantas clases en patrones de diseño?

204

Soy desarrollador junior entre personas mayores y me cuesta mucho entender su razonamiento y razonamiento.

Estoy leyendo Diseño impulsado por dominio (DDD) y no entiendo por qué necesitamos crear tan muchas clases. Si seguimos ese método de diseño de software, terminamos con 20-30 clases que pueden ser reemplazadas por dos archivos y funciones 3-4 como máximo. Sí, esto podría ser complicado, pero es mucho más fácil de mantener y leer.

Cada vez que quiero ver lo que hace algún tipo de EntityTransformationServiceImpl , debo seguir muchas clases, interfaces, sus llamadas a funciones, constructores, su creación, etc.

Matemáticas simples:

  • 60 líneas de código ficticio frente a 10 clases X 10 (digamos que tenemos tales lógicas totalmente diferentes) = 600 líneas de código desordenado frente a 100 clases + algunas más para ajustarlas y administrarlas; no olvide agregar la inyección de dependencia.
  • Leyendo 600 líneas de código desordenado = un día
  • 100 clases = una semana, todavía olvida cuál hace qué, cuándo

Todo el mundo dice 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, si escribe 50K LOC código desordenado en un mes, el DDD requiere muchas revisiones y cambios (no me importan las pruebas en ambos casos). Una simple adición puede tomar una semana o más.

En un año, escribes muchos códigos desordenados e incluso puedes reescribirlos varias veces, pero con el estilo DDD, aún no tienes suficientes funciones para competir con el código desordenado.

Por favor explique. ¿Por qué necesitamos este estilo DDD y muchos patrones?

UPD 1 : Recibí tantas respuestas geniales, ¿pueden ustedes, por favor, agregar comentarios en algún lugar o editar su respuesta con el enlace para leer la lista (no estoy seguro de por dónde comenzar, DDD, Patrones de diseño, UML, Code Complete, Refactoring, Pragmatic, ... tantos buenos libros), por supuesto, con secuencia, para que también pueda comenzar a entender y ser superior como lo hacen algunos de ustedes.

    
pregunta user1318496 10.04.2018 - 18:10

10 respuestas

326

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 de su 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. Es solo una pila, y es muy fácil agregar cosas. Mi esposa lo preferiría si lo optimizase para 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: genere una tonelada de código monolítico lo más rápido posible, sin preocuparse de organizar eso. Como ya has notado, esto puede ser muy, muy rápido. La alternativa es optimizar para el mantenimiento: hacer que la creación sea un poco más difícil, pero hacer las modificaciones 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. Ingenieros experimentados han visto cómo las bases de código no estructuradas cobran vida propia y se convierten en productos, que crecen en tamaño y complejidad, hasta que incluso los pequeños cambios son muy difíciles de realizar sin presentar un gran riesgo. Si el código estuviera estructurado, el riesgo puede ser contenido. Es por eso que nos metemos en todo este problema.

La complejidad proviene de las relaciones, no de los elementos

En su análisis, observo que está viendo cantidades: cantidad de código, número de clases, etc. Mientras que estos son interesantes, el impacto real proviene de las relaciones entre los elementos, que explotan de manera combinatoria. Por ejemplo, si tiene 10 funciones y no tiene idea de cuál depende, tiene 90 relaciones posibles (dependencias) de las que tiene que 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 programadores tienen un montón de cosas de qué preocuparse al resolver un problema en particular. En contraste, si tiene 30 clases pero están organizadas inteligentemente, pueden tener tan solo 29 relaciones, por ejemplo. si están en capas o dispuestos 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 malabares 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 ajustadamente posible, y usamos SOLID principios.

    
respondido por el John Wu 10.04.2018 - 20:51
65

En primer lugar, la legibilidad y la facilidad de mantenimiento están a menudo en el ojo del espectador.

Lo que es legible para usted puede no serlo para su vecino.

La capacidad de mantenimiento 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 maneras en que DDD ayuda a los equipos de desarrolladores es al sugerir una forma específica (aunque 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 mantener la aplicación.

  • Los conceptos de dominio se codifican como Entidades y Agregados
  • El comportamiento del dominio reside en Entidades o Servicios de dominio
  • La consistencia está garantizada por las raíces agregadas
  • Los repositorios manejan las preocupaciones de persistencia

Este arreglo no es objetivamente más fácil de mantener. Sin embargo, es mensurable más fácil de mantener cuando todos comprenden que operan en un contexto DDD.

Clases

Las clases ayudan con la mantenibilidad, la legibilidad, la capacidad de descubrimiento, etc. porque son una convención muy conocida.

En la configuración orientada a objetos, las clases generalmente se usan para agrupar comportamientos estrechamente relacionados y para encapsular el estado que debe controlarse cuidadosamente.

Sé que suena muy abstracto, pero puedes pensarlo de esta manera:

Con Clases, no necesariamente necesitas saber cómo 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 acerca de cómo funciona su aplicación. En lugar de tener que recordar lo que logra 600 líneas de código , puedes pensar en cómo interactúan 30 componentes .

Y, considerando que los 30 componentes probablemente abarcan 3 capas de su aplicación, es probable que solo tenga que estar razonando sobre aproximadamente 10 componentes a la vez.

Eso parece bastante manejable.

Resumen

Esencialmente, lo que estás viendo que hacen los desarrolladores senior es esto:

Están dividiendo la aplicación en clases fáciles de razonar .

A continuación, 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 ella como un todo. Dividirlo en capas y clases significa que nunca tienen que razonar acerca de toda la aplicación. Solo necesitan razonar sobre un pequeño subconjunto de él.

    
respondido por el MetaFight 10.04.2018 - 18:35
29
  

Por favor, explícame, ¿por qué necesitamos este estilo de DDD, muchos patrones?

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 orden incorrecto .

Pero para su pregunta específica: tiende a haber muchas más clases de las que usted esperaría, (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 asegúrese de que los conceptos en el modelo de dominio se expresan explícitamente.

Bruscamente, si tiene dos conceptos diferentes en el dominio, entonces deben ser distintos en el modelo , incluso si comparten el mismo en la representación de memoria .

En efecto, está creando un lenguaje específico para el 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, ve que se presta más atención a la separación de preocupaciones; y la noción de aislar a los consumidores de alguna capacidad a partir de los detalles de la implementación. Consulte D. L. Parnas . Los límites claros le permiten cambiar o extender la implementación sin que los efectos se extiendan por toda la solución.

La motivación aquí: para una aplicación que forma parte de la competencia central de una empresa (es decir, un lugar donde se obtiene una ventaja competitiva), deseará poder reemplazar un comportamiento de dominio de forma fácil y económica 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 unir inadvertidamente una con la otra.

Para ser justos: parte de esto también es daño cerebral orientado a objetos. Los patrones descritos originalmente por Evans se basan en proyectos de Java en los que participó hace más de 15 años; el estado y el comportamiento están estrechamente relacionados con ese estilo, lo que conduce a complicaciones que tal vez prefiera evitar; vea Perception and Action de Stuart Halloway, o Código de inscripción por John Carmack.

  

No importa en qué idioma trabaje, la programación en un estilo funcional ofrece beneficios. Debe hacerlo siempre que sea conveniente, y debe pensar seriamente sobre la decisión cuando no sea conveniente. Carmack, 2012

    
respondido por el VoiceOfUnreason 10.04.2018 - 18:38
29

Hay muchos puntos positivos en las otras respuestas, pero creo que faltan o no enfatizan un error conceptual importante que usted comete:

Estás comparando el esfuerzo por comprender el programa completo.

Esta no es una tarea realista con la mayoría de los programas. Incluso los programas simples constan de tanto código que es simplemente imposible gestionarlos todos en la cabeza en un momento dado. Su única posibilidad es encontrar la parte de un programa que sea relevante para la tarea en cuestión (corregir un error, implementar una nueva función) y trabajar con eso.

Si su programa consta de funciones / métodos / clases enormes, esto es casi imposible. Tendrá que entender cientos de líneas de código para decidir si esta parte del código es relevante para su problema. Con las estimaciones que proporcionó, 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 una pequeña función / métodos / clases nombradas y organizadas en paquetes / espacios de nombres que hace que sea obvio dónde encontrar / poner una pieza de lógica determinada. Cuando se realiza correctamente en muchos casos, puede saltar 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 el rendimiento para tareas comparables y un tamaño de sistema comparable.

El efecto que esto tiene en otras actividades:

  • las pruebas son mucho más fáciles con unidades más pequeñas
  • menos conflictos de fusión porque las posibilidades de que dos desarrolladores trabajen en el mismo código son menores.
  • menos duplicación porque es más fácil reutilizar las piezas (y encontrarlas en primer lugar).
respondido por el Jens Schauder 11.04.2018 - 08:05
19

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, al costo de hacer que el código sea más exigente para escribir en primer lugar.

Sin embargo, hay otro aspecto a considerar: las pruebas solo pueden ser detalladas 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, tiene el alcance de "en algún lugar en esa pila 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 trabajar en una solución. Esto requiere muchos recursos y es un mal uso del tiempo de un desarrollador. Imagina que un pequeño error que tienes, hace que un desarrollador necesite depurar todo el programa nuevamente.

La solución: muchas pruebas más pequeñas que señalan un fallo específico, específico para 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 todas sus pruebas pequeñas fallan, puede encontrar la falla más fácilmente 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 la base de código debe dividirse en partes más pequeñas y comprobables. La forma de hacerlo, en una gran base de código comercial, a menudo da como resultado un código que se parece a lo que está trabajando.

Solo como una nota al margen: Esto no quiere decir que la gente no llevará 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.

    
respondido por el Bilkokuya 11.04.2018 - 15:09
17
  

Por favor, explícame, ¿por qué necesitamos este estilo de DDD, muchos patrones?

Muchos (la mayoría ...) de nosotros realmente no los necesitan . Teóricos y programadores experimentados y muy avanzados escriben libros sobre teorías y metodologías como resultado de mucha investigación y su profunda experiencia, lo que 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 sénior utilicen terminología con la que no está familiarizado. Si encuentra algo muy difícil y no parece que tenga sentido o parezca útil, no se suicide por ello, solo guárdelo en la cabeza de que existe tal concepto o enfoque.

En su desarrollo del día a día, a menos que sea un académico, su trabajo es encontrar soluciones viables y sostenibles. Si las ideas que encuentras en un libro no te ayudan a lograr ese objetivo, entonces no te preocupes por eso ahora, siempre y cuando tu trabajo se considere satisfactorio.

Puede llegar un momento en el que descubras que puedes usar algo de lo que leíste pero que al principio no "entendiste", o tal vez no.

    
respondido por el Vector 11.04.2018 - 00:53
8

Como su pregunta abarca mucho terreno, con muchas suposiciones, señalaré el tema de su pregunta:

  

¿Por qué necesitamos tantas clases en patrones de diseño

No lo hacemos. No hay 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 dividir tus tareas en diferentes unidades de código:

  • Cohesión: cualquier unidad de código (ya sea un paquete, un archivo, una clase o un método) debe pertenecer juntas . Es decir, cualquier método específico debería tener una tarea, y hacerlo bien. Cualquier clase debe ser responsable de un tema más grande (cualquiera que sea). Queremos alta cohesión.
  • Acoplamiento: cualquiera de las dos unidades de código deben depender lo menos la una de la otra, especialmente no debe haber dependencias circulares. Queremos acoplamiento bajo.

¿Por qué deberían ser importantes esos dos?

  • Cohesión: un método que hace muchas cosas (por ejemplo, una secuencia de comandos CGI pasada de moda que hace GUI, lógica, acceso a bases de datos, etc., todo en un largo lío de código) se vuelve difícil de manejar. Al momento de escribir, es tentador simplemente poner tu tren de pensamiento en un método largo. Esto funciona, es fácil de presentar y demás, y se puede hacer con él. El problema surge más tarde: después de unos meses, puedes olvidar lo que hiciste. Una línea de código en la parte superior puede estar a unas pocas pantallas de distancia de una línea en la parte inferior; Es fácil olvidar todos los detalles. Cualquier cambio en cualquier parte del método puede romper cualquier cantidad de comportamientos de lo complejo. Será bastante fácil refactorizar o reutilizar partes del método. Y así sucesivamente.
  • Acoplamiento: cada vez que cambias una unidad de código, potencialmente rompes todas las demás unidades que dependen de ella. En lenguajes estrictos como Java, puede obtener sugerencias durante el tiempo de compilación (es decir, sobre parámetros, excepciones declaradas, etc.). Pero muchos cambios no están provocando tales (es decir, cambios de comportamiento), y otros idiomas más dinámicos no tienen tales posibilidades. Cuanto más alto sea el acoplamiento, más difícil será cambiar algo, y podría detenerse, donde es necesaria una reescritura completa para lograr algún objetivo.

Estos dos aspectos son los "controladores" básicos para cualquier opción de "dónde colocar qué" en cualquier lenguaje de programación y cualquier paradigma (no solo OO). No todo el mundo está explícitamente al tanto de ellos, y lleva tiempo, a menudo años, obtener una sensación realmente arraigada de cómo estos influyen en el software.

Obviamente, esos dos conceptos no te dicen nada sobre lo que realmente debes hacer hacer . Algunas personas se equivocan por demasiado, otras por 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 hace especialmente notable cuando lo comparas con lenguajes dinámicos y más expresivos, por ejemplo, Ruby.

Otro aspecto es que algunas personas se suscriben al enfoque ágil de solo escribir el 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 un interface cuando solo tenga una clase de implementación. Simplemente implementarías la clase concreta. Si, más tarde, necesitas una segunda clase, te refactorizarás.

Algunas personas simplemente no funcionan de esa manera. Crean interfaces (o, más generalmente, clases base abstractas) para cualquier cosa que 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, o usted, prefiera. En su vida como desarrollador de software, se encontrará con todos los extremos, desde métodos de spaghetti largos, a través de diseños de clase ilustrados, lo suficientemente grandes, hasta esquemas de clase increíblemente volados que están muy desarrollados. A medida que adquiera más experiencia, crecerá más en roles "arquitectónicos" y podrá comenzar a influir en esto en la dirección que desee. Descubrirás un medio dorado por ti mismo, y todavía encontrarás que muchas personas no estarán de acuerdo contigo, hagas lo que hagas.

Por lo tanto, mantener una mente abierta es lo más importante aquí, y ese sería mi principal consejo para ti, ya que parece que te duele mucho el tema, a juzgar por el resto de tu pregunta ...

    
respondido por el AnoE 11.04.2018 - 19:22
7

Los codificadores experimentados han aprendido:

  • Porque esos programas y clases pequeñas comienzan a crecer, especialmente los exitosos. Los patrones simples que funcionaron a un nivel simple no se escalan.
  • Porque puede parecer complicado tener que agregar / cambiar varios artefactos para cada adición / cambio, pero usted sabe qué agregar y es fácil hacerlo. 3 minutos de escritura superan las 3 horas de codificación inteligente.
  • Muchas clases pequeñas no son "desordenadas" simplemente porque no entiendes por qué la sobrecarga es realmente una buena idea
  • Sin el conocimiento de dominio agregado, el código 'obvio para mí' es a menudo misterioso para mis compañeros de equipo ... más el futuro para mí.
  • El conocimiento tribal puede hacer que los proyectos sean increíblemente difíciles de agregar a los miembros del equipo con facilidad y dificultad para que sean productivos rápidamente.
  • El nombramiento es uno de los dos problemas difíciles de computación que aún son verdaderos, y muchas clases y métodos son a menudo un ejercicio intenso de asignación de nombres que muchos consideran un gran beneficio.
respondido por el Michael Durrant 11.04.2018 - 04:06
2

Las respuestas hasta ahora, todas buenas, habiendo comenzado con la suposición razonable de que al que 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 analizar cómo puede darse esta situación.

La experiencia y la práctica son poderosas, y si los adultos mayores obtuvieron su experiencia en proyectos grandes y complejos donde la única manera de mantener las cosas bajo control es con un montón de EntityTransformationServiceImpl 's, entonces se vuelven rápidos y cómodos con los patrones de diseño y estrecha adhesión a la DDD. Serían mucho menos efectivos utilizando un enfoque ligero, incluso para programas pequeños. Como impar, debes adaptarte y será una gran experiencia de aprendizaje.

No obstante, mientras se adapta, debe tomarlo como una lección de equilibrio entre aprender un solo enfoque en profundidad, hasta el punto en que pueda hacerlo funcionar en cualquier lugar, en lugar de mantenerse versátil y saber qué herramientas están disponibles sin ser necesariamente un experto en de ellos. Hay ventajas para ambos y ambos son necesarios en el mundo.

    
respondido por el Josh Rumbut 13.04.2018 - 08:22
-4

Hacer más clase y función según el uso será un excelente enfoque para resolver problemas de una manera específica que ayudará en el futuro a resolver cualquier problema.

Múltiples 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 de su nombre de tabla, son fáciles de llamar y administrar. Esto se llama código limpio.

    
respondido por el Sanjeev Chauhan 13.04.2018 - 17:16

Lea otras preguntas en las etiquetas