¿Hay sabores de OOP donde algunos o todos los principios SÓLIDOS son antitéticos al código limpio?

26

Recientemente tuve una discusión con un amigo sobre OOP en el desarrollo de videojuegos.

Estaba explicando la arquitectura de uno de mis juegos que, para sorpresa de mi amigo, contenía muchas clases pequeñas y varias capas de abstracción. Argumenté que este era el resultado de concentrarme en darle a todo una responsabilidad única y también de aflojar el acoplamiento entre los componentes.

Su preocupación era que la gran cantidad de clases se traduciría en una pesadilla de mantenimiento. Mi opinión era que tendría exactamente el efecto contrario. Procedimos a discutir esto durante lo que parecieron siglos, y finalmente acordamos estar en desacuerdo, diciendo que tal vez hubo casos en los que los principios SÓLIDOS y la POO adecuada en realidad no se mezclaron bien.

Incluso la entrada de Wikipedia sobre principios SÓLIDOS establece que son pautas que ayudan a escribir código mantenible y que son parte de una estrategia general de programación ágil y adaptativa.

Entonces, mi pregunta es:

¿Hay casos en OOP donde algunos o todos los principios SOLIDOS no se prestan para limpiar el código?

Puedo imaginar de inmediato que el Principio de sustitución de Liskov podría entrar en conflicto con otro sabor de herencia segura. Es decir, si alguien ideó otro patrón útil implementado a través de la herencia, es muy posible que el LSP esté en conflicto directo con él.

¿Hay otros? ¿Quizás ciertos tipos de proyectos o ciertas plataformas objetivo funcionan mejor con un enfoque menos SÓLIDO?

Editar:

Solo me gustaría especificar que no estoy preguntando cómo mejorar mi código;) La única razón por la que mencioné un proyecto en esta pregunta fue para dar un poco de contexto. Mi pregunta es sobre OOP y principios de diseño en general .

Si tienes curiosidad sobre mi proyecto, mira esto .

Edición 2:

Me imaginé que esta pregunta sería respondida de una de las 3 maneras siguientes:

  1. Sí, existen principios de diseño de OOP que están parcialmente en conflicto con SOLID
  2. Sí, existen principios de diseño de OOP que están completamente en conflicto con SOLID
  3. No, SOLID es las rodillas de la abeja y OOP siempre será mejor con ella. Pero, como con todo, no es una panacea. Bebe responsablemente.

Las opciones 1 y 2 probablemente habrían generado respuestas largas e interesantes. La opción 3, por otro lado, sería una respuesta corta, poco interesante, pero en general tranquilizadora.

Parece que estamos convergiendo en la opción 3.

MetaFight
fuente
¿Cómo estás definiendo 'código limpio'?
GrandmasterB
El código limpio, en el ámbito de esta pregunta, es la estructura del código de una manera que cumple los objetivos de OOP y un conjunto de principios de diseño elegidos. Entonces, estoy familiarizado con el código limpio en términos de los objetivos establecidos por OOP + SOLID. Me pregunto si hay otro conjunto de principios de diseño que funcionan con OOP pero son total o parcialmente incompatibles con SOLID.
MetaFight
1
Me preocupa mucho más el rendimiento de ese juego tuyo que cualquier otra cosa. A menos que estemos hablando de un juego basado en texto.
Eufórico
1
Este juego es esencialmente un experimento. Estoy probando diferentes enfoques de desarrollo, y también estoy tratando de ver si el código empresarial puede funcionar al escribir un juego. Espero que el rendimiento se convierta en un problema, pero aún no lo ha sido. Si / cuando lo hace, entonces lo siguiente que experimentaré es cómo adaptar mi código al código de rendimiento. ¡Tanto aprendizaje!
MetaFight
1
A riesgo de algunos votos negativos, me gustaría volver a la pregunta del "por qué": POO en el desarrollo de juegos . Debido a la naturaleza del desarrollo del juego, la OOP "tradicional" parece estar cayendo de moda a favor de varios sabores de sistemas de entidad / componente, una variante interesante aquí, por ejemplo. Esto más bien me hace pensar que la pregunta no debería ser "SOLID + OOP" sino más bien, "OOP + Game Development". Cuando miras un gráfico de interacción de objetos para el desarrollador del juego. frente a la aplicación de nivel N, las diferencias pueden significar que un nuevo paradigma es bueno.
J Trana

Respuestas:

20

¿Hay casos en OOP donde algunos o todos los principios SOLIDOS no se prestan para limpiar el código?

En general, no. La historia ha demostrado que los principios SÓLIDOS contribuyen en gran medida a un mayor desacoplamiento, lo que a su vez ha demostrado que aumenta la flexibilidad en el código y, por lo tanto, su capacidad para adaptarse al cambio y hacer que el código sea más fácil de razonar, probar y reutilizar. En resumen, haga su código más limpio.

Ahora, puede haber casos en los que los principios SÓLIDOS colisionen con DRY (no se repita), KISS (manténgalo simple estúpido) u otros principios de buen diseño OO. Y, por supuesto, pueden colisionar con la realidad de los requisitos, las limitaciones de los humanos, las limitaciones de nuestros lenguajes de programación, otros obstáculos.

En resumen, los principios SOLID siempre se prestarán para limpiar el código, pero en algunos escenarios se prestarán menos que alternativas conflictivas. Siempre son buenos, pero a veces otras cosas son más buenas.

Telastyn
fuente
14
Me gusta pero a veces otras cosas son más buenas . Tiene una sensación de "Animal Farm". ;)
FrustratedWithFormsDesigner
12
+1 Cuando acabo de mirar los principios SÓLIDOS, veo 5 "buenos para tener". Pero ninguno de ellos supera a DRY, KISS y "deja en claro tus intenciones". Use SOLID, pero muestre cierta precaución y escepticismo.
user949300
Estoy de acuerdo. Creo que los principios correctos a seguir dependen claramente de la situación, pero aparte de los requisitos de rendimiento, que siempre se encuentran por encima de todo lo demás (esto es especialmente importante en el desarrollo del juego), tiendo a pensar que DRY y KISS suelen ser más importantes que SÓLIDO Por supuesto, cuanto más limpio haga el código, mejor, por lo que si puede seguir todos los principios sin conflictos, mejor.
Ben Lee
¿Qué pasa con la segregación de integración versus el diseño eficiente de agregados? Si la interfaz básica de "secuencia" [por ejemplo, IEnumerable] incluye métodos como County asImmutable, y propiedades como getAbilities[cuyo retorno indicaría si cosas como Countserán "eficientes"], entonces uno podría tener un método estático que tome múltiples secuencias y las agregue para que Se comportará como una sola secuencia más larga. Si tales habilidades están presentes en el tipo de secuencia básica, incluso si solo se encadenan a implementaciones predeterminadas, entonces un agregado podrá exponer esas habilidades ...
supercat
... e implementarlos de manera eficiente cuando se usa con clases base que pueden hacerlo. Si uno agrega una lista de diez millones de artículos que conoce su recuento, y una lista de cinco artículos que solo puede recuperar artículos secuencialmente, una solicitud para la entrada 10,000,003 debe leer el recuento de la primera lista y luego leer tres artículos de la segunda . Las interfaces de fregadero de cocina pueden violar el ISP, pero podrían mejorar en gran medida el rendimiento de algunos escenarios de composición / agregación.
supercat
13

Creo que tengo una perspectiva inusual en el sentido de que he trabajado tanto en finanzas como en juegos.

Muchos de los programadores que conocí en juegos eran terribles en Ingeniería de Software, pero no necesitaban prácticas como SOLID. Tradicionalmente, ponen su juego en una caja, luego terminan.

En finanzas, descubrí que los desarrolladores pueden ser realmente descuidados e indisciplinados porque no necesitan el rendimiento que tú haces en los juegos.

Ambas declaraciones anteriores son, por supuesto, generalizaciones excesivas, pero en realidad, en ambos casos, el código limpio es crítico. Para la optimización, es esencial un código claro y fácil de entender. Por el bien de la mantenibilidad, quieres lo mismo.

Eso no quiere decir que SOLID no esté exento de críticas. Se llaman principios y, sin embargo, se supone que deben seguirse como directrices. Entonces, ¿cuándo exactamente debo seguirlos? ¿Cuándo debo romper las reglas?

Probablemente podría mirar dos piezas de código y decir cuál cumple mejor con SOLID. Pero de forma aislada, no hay una forma objetiva de transformar el código para que sea SÓLIDO. Definitivamente es a la interpretación del desarrollador.

No es sequitur decir que "un gran número de clases puede llevar a una pesadilla de mantenimiento". Sin embargo, si SRP no se interpreta correctamente, podría terminar fácilmente con este problema.

He visto código con muchas capas de casi ninguna responsabilidad. Las clases que no hacen nada aparte de pasar el estado de una clase a otra. El problema clásico de demasiadas capas de indirección.

SRP si se abusa puede terminar con una falta de cohesión en su código. Puede decir esto cuando intenta agregar una función. Si siempre tiene que cambiar varios lugares al mismo tiempo, su código carece de cohesión.

Open-Closed no está exento de críticas. Por ejemplo, vea la revisión de Jon Skeet del principio de Open Closed . No reproduciré sus argumentos aquí.

Su argumento sobre LSP parece bastante hipotético y realmente no entiendo lo que quiere decir con otra forma de herencia segura.

ISP parece duplicar algo SRP. Seguramente deben interpretarse de la misma manera que nunca se puede anticipar lo que harán todos los clientes de su interfaz. La interfaz cohesiva de hoy es el violador de ISP de mañana. La única conclusión ilógica es no tener más de un miembro por interfaz.

DIP puede conducir a un código inutilizable. He visto clases con una gran cantidad de parámetros en nombre de DIP. Estas clases normalmente habrían "renovado" sus partes compuestas, pero en nombre de la capacidad de prueba, nunca debemos volver a usar la nueva palabra clave.

SOLID por sí solo no es suficiente para escribir código limpio. He visto el libro de Robert C. Martin, " Código limpio: un manual de artesanía ágil de software ". El mayor problema es que es fácil leer este libro e interpretarlo como reglas. Si haces eso, te estás perdiendo el punto. Contiene una gran lista de olores, heurística y principios, muchos más que solo los cinco de SOLID. Todos estos "principios" no son realmente principios sino pautas. El tío Bob los describe a sí mismo como "agujeros de cubículo" para los conceptos. Son un acto de equilibrio; seguir cualquier directriz a ciegas causará problemas.

Dave Hillier
fuente
1
Si tiene una gran cantidad de parámetros de constructor, sugiere que ha violado el SRP. Sin embargo, un contenedor DI elimina el dolor asociado con una gran cantidad de parámetros de constructor de todos modos, por lo que no estoy de acuerdo con sus declaraciones en DIP.
Stephen
Todos estos "principios" no son realmente principios sino pautas. Son un acto de equilibrio; Como dije en mi publicación después de SRP, el extremo en sí mismo puede ser problemático.
Dave Hillier
Buena respuesta, +1. Una cosa que me gustaría agregar: leí la reseña de Skeet más una crítica de la definición estándar de libros de texto del OCP, no de las ideas centrales detrás del OCP. Según tengo entendido, la idea de variación protegida es lo que el OCP debería haber sido desde el principio.
Doc Brown
5

Aquí está mi opinión:

Aunque los principios SOLID apuntan a una base de código flexible y no redundante, esto podría ser una compensación en la legibilidad y el mantenimiento si hay demasiadas clases y capas.

Se trata especialmente de la cantidad de abstracciones . Un gran número de clases podría estar bien si se basan en pocas abstracciones. Dado que no conocemos el tamaño y las características específicas de su proyecto, es difícil saberlo, pero es un poco preocupante que mencione varias capas además de una gran cantidad de clases.

Supongo que los principios SOLID no están en desacuerdo con OOP, pero aún es posible escribir código no limpio que se adhiera a ellos.

henginy
fuente
3
+1 Por definición casi, un sistema altamente flexible debe ser menos mantenible que uno que sea menos flexible. La flexibilidad tiene un costo debido a la complejidad adicional que trae.
Andy
@Andy no necesariamente. En mi trabajo actual, muchas de las peores partes del código son desordenadas porque simplemente se combinan "haciendo algo simple, simplemente pirateándolo y póngalo a funcionar para que no se vuelva tan complejo". Como resultado, muchas partes del sistema son 100% rígidas. No puede modificarlos en absoluto sin reescribir grandes partes de ellos. Es realmente difícil de mantener. La mantenibilidad proviene de una alta cohesión y un bajo acoplamiento, no de cuán flexible es el sistema.
sara
3

En mis primeros días de desarrollo, comencé a escribir un Roguelike en C ++. Como quería aplicar la buena metodología orientada a objetos que aprendí de mi educación, inmediatamente vi los elementos del juego como objetos de C ++. Potion, Swords, Food, Axe, JavelinsEtc, todo derivado de un núcleo básico Itemde código, nombre, icono, peso, ese tipo de cosas manipulación.

Luego, codifiqué la lógica de la bolsa y enfrenté un problema. Puedo poner Itemsen la bolsa, pero luego, si elijo una, ¿cómo sé si es una Potiono una Sword? Busqué en Internet cómo rechazar artículos. Pirateé las opciones del compilador para habilitar la información de tiempo de ejecución para saber cuáles son mis Itemtipos verdaderos abstraídos , y comencé a cambiar la lógica de los tipos para saber qué operaciones permitiría (por ejemplo, evitar beber Swordsy ponerme de Potionspie)

Básicamente, convirtió el código en una gran bola de lodo medio copiado. A medida que avanzaba el proyecto, comencé a comprender cuál era el fallo del diseño. Lo que sucedió es que intenté usar clases para representar valores. Mi jerarquía de clases no era una abstracción significativa. Lo que debería haber hecho es hacer Itemponer en práctica un conjunto de funciones, como por ejemplo Equip (), Apply ()y Throw (), y hacer que cada uno se comporte basado en valores. Usar enumeraciones para representar máquinas tragamonedas. Usar enumeraciones para representar diferentes tipos de armas. Usando más valores y menos clasificación, porque mi subclasificación no tenía otro propósito que llenar estos valores finales.

Como se enfrenta a la multiplicación de objetos bajo una capa de abstracción, creo que mi visión puede ser valiosa aquí. Si parece tener demasiados tipos de objetos, es posible que esté confundiendo lo que deberían ser tipos con lo que deberían ser valores.

Arthur Havlicek
fuente
2
El problema era confundir los tipos con las clases de valores (nota: no estoy usando la palabra clase para referirme a la noción de clase OOP / C ++). Hay más de una forma de representar un punto en un plano cartesiano (coordenadas rectangulares, polares coordenadas) pero todas son parte del mismo tipo. Sin embargo, los lenguajes OOP convencionales no admiten la clasificación de valores de forma natural. Lo más parecido a eso es la unión de C / C ++, pero debes agregar un campo adicional solo para saber qué diablos pusiste allí.
Doval
44
Su experiencia puede ser utilizada como anti-ejemplo. Al no utilizar SOLID ni ningún tipo de metodología de modelado, creó un código insostenible e inextensible.
Eufórico
1
@Eufórico Lo intenté mucho. Tenía una metodología orientada a objetos. Sin embargo, hay una diferencia entre tener una metodología e implementarla con éxito :)
Arthur Havlicek
2
¿Cómo es este un ejemplo de principios SÓLIDOS que se ejecutan en contra del código limpio en OOP? Parece más un ejemplo de un diseño incorrecto: ¡esto es ortogonal a OOP!
Andres F.
1
Su clase ITEM base debería haber tenido una cantidad de métodos booleanos "canXXXX", todos predeterminados a FALSE. Luego puede configurar "canThrow ()" para que sea verdadero en su clase Javelin, y canEat () para que sea verdadero para su clase de Postion.
James Anderson
3

Su preocupación era que la gran cantidad de clases se traduciría en una pesadilla de mantenimiento. Mi opinión era que tendría exactamente el efecto contrario.

Estoy absolutamente del lado de tu amigo, pero podría ser una cuestión de nuestros dominios y los tipos de problemas y diseños que abordamos y especialmente qué tipos de cosas pueden requerir cambios en el futuro. Diferentes problemas, diferentes soluciones. No creo en lo correcto o incorrecto, solo en los programadores que intentan encontrar la mejor manera de resolver sus problemas de diseño particulares. Trabajo en VFX, que no es muy diferente de los motores de juego.

Pero el problema para mí con el que luché en lo que al menos podría llamarse algo más de una arquitectura de conformidad SÓLIDA (estaba basado en COM), podría resumirse en "demasiadas clases" o "demasiadas funciones" como tu amigo podría describirlo. Diría específicamente, "demasiadas interacciones, demasiados lugares que podrían comportarse mal, demasiados lugares que podrían causar efectos secundarios, demasiados lugares que podrían necesitar cambiar y demasiados lugares que podrían no hacer lo que creemos que hacen". ".

Tuvimos un puñado de interfaces abstractas (y puras) implementadas por una gran cantidad de subtipos, así (hicimos este diagrama en el contexto de hablar sobre los beneficios de ECS, sin tener en cuenta el comentario de abajo a la izquierda):

ingrese la descripción de la imagen aquí

Donde una interfaz de movimiento o una interfaz de nodo de escena podría implementarse por cientos de subtipos: luces, cámaras, mallas, solucionadores físicos, sombreadores, texturas, huesos, formas primitivas, curvas, etc., etc. (y a menudo había múltiples tipos de cada uno ) Y el problema final era que esos diseños no eran tan estables. Tuvimos requisitos cambiantes y, a veces, las interfaces mismas tuvieron que cambiar, y cuando desea cambiar una interfaz abstracta implementada por 200 subtipos, es un cambio extremadamente costoso. Comenzamos a mitigar eso mediante el uso de clases base abstractas entre las cuales se redujeron los costos de dichos cambios de diseño, pero aún así eran caros.

Así que, alternativamente, comencé a explorar la arquitectura del sistema de componentes de entidad que se usa con bastante frecuencia en la industria del juego. Eso cambió todo para ser así:

ingrese la descripción de la imagen aquí

Y guau! Esa fue una gran diferencia en términos de mantenibilidad. Las dependencias ya no fluían hacia abstracciones , sino hacia datos (componentes). Y al menos en mi caso, los datos eran mucho más estables y más fáciles de acertar en términos de diseño por adelantado a pesar de los requisitos cambiantes (aunque lo que podemos hacer con los mismos datos cambia constantemente con los requisitos cambiantes).

Además, debido a que las entidades en un ECS usan composición en lugar de herencia, en realidad no necesitan contener funcionalidad. Son simplemente el "contenedor de componentes" analógico. Eso hizo que los 200 subtipos analógicos que implementaron una interfaz de movimiento se conviertan en 200 instancias de entidad (no tipos separados con código separado) que simplemente almacenan un componente de movimiento (que no es más que datos asociados con el movimiento). A PointLightya no es una clase / subtipo separado. No es una clase en absoluto. Es una instancia de una entidad que solo combina algunos componentes (datos) relacionados con su ubicación en el espacio (movimiento) y las propiedades específicas de las luces de punto. La única funcionalidad asociada con ellos está dentro de los sistemas, como elRenderSystem, que busca componentes de luz en la escena para determinar cómo representar la escena.

Con los requisitos cambiantes bajo el enfoque de ECS, a menudo solo era necesario cambiar uno o dos sistemas que funcionan con esos datos o simplemente introducir un nuevo sistema en el lateral, o introducir un nuevo componente si se necesitaban nuevos datos.

Entonces, al menos para mi dominio, y estoy casi seguro de que no es para todos, esto hizo las cosas mucho más fáciles porque las dependencias fluían hacia la estabilidad (cosas que no necesitaban cambiar a menudo). Ese no era el caso en la arquitectura COM cuando las dependencias fluían uniformemente hacia las abstracciones. En mi caso, es mucho más fácil averiguar qué datos se requieren para el movimiento por adelantado en lugar de todas las cosas posibles que podría hacer con él, que a menudo cambia un poco a lo largo de los meses o años a medida que entran nuevos requisitos.

ingrese la descripción de la imagen aquí

¿Hay casos en OOP donde algunos o todos los principios SOLIDOS no se prestan para limpiar el código?

Bueno, no puedo decir código limpio, ya que algunas personas equiparan el código limpio con SOLID, pero definitivamente hay algunos casos en los que separar los datos de la funcionalidad como lo hace el ECS, y redirigir las dependencias de las abstracciones hacia los datos definitivamente puede hacer que las cosas sean mucho más fáciles. cambiar, por razones obvias de acoplamiento, si los datos van a ser mucho más estables que las abstracciones. Por supuesto, las dependencias de los datos pueden dificultar el mantenimiento de invariantes, pero ECS tiende a mitigarlo al mínimo con la organización del sistema, lo que minimiza el número de sistemas que acceden a cualquier tipo de componente.

No es necesariamente que las dependencias fluyan hacia abstracciones, como sugeriría DIP; las dependencias deben fluir hacia cosas que es muy poco probable que necesiten cambios futuros. Eso puede o no ser abstracciones en todos los casos (ciertamente no estaba en el mío).

  1. Sí, existen principios de diseño de OOP que están parcialmente en conflicto con SOLID
  2. Sí, existen principios de diseño de OOP que están completamente en conflicto con SOLID.

No estoy seguro de si ECS es realmente un sabor de OOP. Algunas personas lo definen de esa manera, pero lo veo muy diferente inherentemente con las características de acoplamiento y la separación de datos (componentes) de la funcionalidad (sistemas) y la falta de encapsulación de datos. Si se considera una forma de OOP, creo que está muy en conflicto con SOLID (al menos las ideas más estrictas de SRP, abierto / cerrado, sustitución de liskov y DIP). Pero espero que este sea un ejemplo razonable de un caso y dominio en el que los aspectos más fundamentales de SOLID, al menos como la gente generalmente los interpretaría en un contexto OOP más reconocible, podrían no ser tan aplicables.

Clases pequeñitas

Estaba explicando la arquitectura de uno de mis juegos que, para sorpresa de mi amigo, contenía muchas clases pequeñas y varias capas de abstracción. Argumenté que este era el resultado de concentrarme en darle a todo una responsabilidad única y también de aflojar el acoplamiento entre los componentes.

El ECS ha desafiado y cambiado mucho mis puntos de vista. Al igual que usted, solía pensar que la idea misma de la capacidad de mantenimiento es tener la implementación más simple posible para las cosas, lo que implica muchas cosas y, además, muchas cosas interdependientes (incluso si las interdependencias están entre abstracciones). Tiene más sentido si está haciendo zoom en una sola clase o función para querer ver la implementación más sencilla y simple, y si no vemos una, refactorícela e incluso descomponga aún más. Pero puede ser fácil perderse lo que está sucediendo con el mundo exterior como resultado, porque cada vez que divide algo relativamente complejo en 2 o más cosas, esas 2 o más cosas deben interactuar inevitablemente * (ver más abajo) entre sí en algunos camino, o algo afuera tiene que interactuar con todos ellos.

En estos días encuentro que hay un acto de equilibrio entre la simplicidad de algo y cuántas cosas hay y cuánta interacción se requiere. Los sistemas en un ECS tienden a ser bastante pesados ​​con implementaciones no triviales para operar en los datos, como PhysicsSystemo RenderSystemo GuiLayoutSystem. Sin embargo, el hecho de que un producto complejo necesite tan pocos de ellos tiende a facilitar el paso atrás y razonar sobre el comportamiento general de toda la base de código. Hay algo allí que podría sugerir que podría no ser una mala idea apoyarse en menos clases más voluminosas (aún desempeñando una responsabilidad posiblemente discutible), si eso significa menos clases para mantener y razonar, y menos interacciones a lo largo el sistema.

Interacciones

Digo "interacciones" en lugar de "acoplamiento" (aunque reducir interacciones implica reducir ambas), ya que puedes usar abstracciones para desacoplar dos objetos concretos, pero aún se hablan entre sí. Todavía podrían causar efectos secundarios en el proceso de esta comunicación indirecta. Y a menudo encuentro que mi capacidad de razonar sobre la corrección de un sistema está más relacionada con estas "interacciones" que con el "acoplamiento". Minimizar las interacciones tiende a hacerme las cosas mucho más fáciles de razonar sobre todo desde la vista de pájaro. Eso significa que las cosas no se hablan entre sí, y desde ese sentido, ECS también tiende a minimizar realmente las "interacciones", y no solo el acoplamiento, a los mínimos más mínimos (al menos no tengo

Dicho esto, esto podría ser al menos parcialmente yo y mis debilidades personales. He encontrado que el mayor impedimento para crear sistemas de enorme escala, y aún así razonar con confianza sobre ellos, navegar a través de ellos y sentir que puedo hacer cualquier cambio deseado en cualquier lugar de manera predecible, es la gestión del estado y los recursos junto con efectos secundarios. Es el mayor obstáculo que comienza a surgir a medida que paso de decenas de miles de LOC a cientos de miles de LOC a millones de LOC, incluso para el código que escribí completamente por mi cuenta. Si algo me va a retrasar por encima de todo lo demás, es esta sensación de que ya no puedo entender lo que está sucediendo en términos de estado de la aplicación, datos y efectos secundarios. Eso' No es el tiempo robótico que requiere hacer un cambio que me frena tanto como la incapacidad de comprender los impactos completos del cambio si el sistema crece más allá de la capacidad de mi mente para razonar sobre ello. Y reducir las interacciones ha sido, para mí, la forma más efectiva de permitir que el producto crezca mucho más con muchas más características sin que yo personalmente me sienta abrumado por estas cosas, ya que reducir las interacciones al mínimo también reduce la cantidad de lugares que pueden incluso posiblemente cambie el estado de la aplicación y cause efectos secundarios de manera sustancial.

Puede convertirse en algo como esto (donde todo en el diagrama tiene funcionalidad, y obviamente un escenario del mundo real tendría muchas, muchas veces la cantidad de objetos, y este es un diagrama de "interacción", no uno de acoplamiento, como un acoplamiento uno tendría abstracciones en el medio):

ingrese la descripción de la imagen aquí

... a esto donde solo los sistemas tienen funcionalidad (los componentes azules ahora son solo datos, y ahora este es un diagrama de acoplamiento):

ingrese la descripción de la imagen aquí

Y están surgiendo pensamientos sobre todo esto y tal vez una forma de enmarcar algunos de estos beneficios en un contexto OOP más conforme que sea más compatible con SOLID, pero todavía no he encontrado los diseños y las palabras, y lo encuentro difícil desde la terminología que estaba acostumbrado a lanzar todo lo relacionado directamente con OOP. Sigo tratando de resolverlo leyendo las respuestas de las personas aquí y también haciendo mi mejor esfuerzo para formular las mías, pero hay algunas cosas muy interesantes sobre la naturaleza de un ECS que no he podido identificar perfectamente. podría ser más ampliamente aplicable incluso a arquitecturas que no lo usan. ¡También espero que esta respuesta no salga como una promoción de ECS! Simplemente me parece muy interesante ya que diseñar un ECS realmente ha cambiado mis pensamientos drásticamente,


fuente
Me gusta la distinción entre interacciones y acoplamiento. Aunque su primer diagrama de interacción está ofuscado. Es un gráfico plano, por lo que no es necesario cruzar flechas.
D Drmmr
2

Los principios SÓLIDOS son buenos, pero KISS y YAGNI tienen prioridad en caso de conflictos. El propósito de SOLID es administrar la complejidad, pero si la aplicación de SOLID en sí hace que el código sea más complejo, se pierde el propósito. Por poner un ejemplo, si tiene un programa pequeño con solo unas pocas clases, la aplicación de DI, la segregación de interfaz, etc. podría aumentar la complejidad general del programa.

El principio abierto / cerrado es especialmente controvertido. Básicamente dice que debe agregar características a una clase creando una subclase o una implementación separada de la misma interfaz, pero sin tocar la clase original. Si mantiene una biblioteca o un servicio utilizado por clientes no coordinados, esto tiene sentido, ya que no desea interrumpir el comportamiento en el que puede confiar el cliente existente. Sin embargo, si está modificando una clase que solo se usa en su propia aplicación, a menudo sería mucho más simple y limpio simplemente modificar la clase.

JacquesB
fuente
Su discusión sobre OCP ignora la posibilidad de extender el comportamiento de una clase a través de la composición (por ejemplo, usando un objeto de estrategia para permitir que se cambie un algoritmo utilizado por la clase), que generalmente se considera una mejor manera de cumplir con el principal que la subclase .
Jules
1
Si KISS y YAGNI siempre tuvieron prioridad, todavía debería codificar en 1 y 0. Los ensambladores son algo más que puede salir mal. Ningún KISS y YAGNI señalan dos de los muchos costos del trabajo de diseño. Ese costo siempre debe equilibrarse con el beneficio de cualquier trabajo de diseño. SOLID tiene beneficios que en algunos casos compensan los costos. No hay una manera simple de saber cuándo has cruzado la línea.
candied_orange
@CandiedOrange: no estoy de acuerdo con que el código de máquina sea más simple que el código en un lenguaje de alto nivel. El código de máquina termina conteniendo una gran cantidad de complejidad accidental debido a la falta de abstracción, por lo que definitivamente no es KISS decidir escribir una aplicación típica en código de máquina.
JacquesB
@Jules: Sí, la composición es preferible, pero aún requiere que diseñe la clase original de tal manera que la composición se pueda usar para personalizarla, por lo tanto, debe hacer suposiciones sobre qué aspectos deben personalizarse en el futuro y qué aspectos No son personalizables. En algunos casos, esto es necesario (por ejemplo, está escribiendo una biblioteca para su distribución) pero tiene un costo, por lo tanto, seguir SOLID no siempre es óptimo.
JacquesB
1
@JacquesB: cierto. Y OCP está en su núcleo opuesto a YAGNI; como quiera que lo logre, requiere que diseñe puntos de extensión para permitir una mejora posterior. Encontrar un punto de equilibrio adecuado entre los dos ideales es ... complicado.
Julio
1

En mi experiencia:-

Las clases grandes son exponencialmente más difíciles de mantener a medida que se hacen más grandes.

Las clases pequeñas son más fáciles de mantener y la dificultad de mantener una colección de clases pequeñas aumenta aritméticamente al número de clases.

A veces las clases grandes son inevitables, un gran problema a veces necesita una clase grande, pero son mucho más difíciles de leer y comprender. Para clases más pequeñas bien nombradas, solo el nombre puede ser suficiente para la comprensión, ni siquiera necesita mirar el código a menos que haya un problema muy específico con la clase.

James Anderson
fuente
0

Siempre me resulta difícil trazar la línea entre la 'S' del sólido y la (puede ser anticuada) la necesidad de dejar que un objeto tenga todo el conocimiento de sí mismo.

Hace 15 años trabajé con desarrolladores de Deplhi que tenían su santo grial para tener clases que contenían todo. Entonces, para una hipoteca, puede agregar seguros e ingresos, préstamos e ingresos e información sobre impuestos, y puede calcular el impuesto a pagar, el impuesto a deducir y puede serializarse, persistir en el disco, etc., etc.

Y ahora tengo el mismo objeto dividido en muchísimas clases más pequeñas que tienen la ventaja de que pueden probarse de forma mucho más fácil y mejor, pero no ofrecen una buena visión general de todo el modelo comercial.

Y sí, sé que no desea vincular sus objetos a la base de datos, pero puede inyectar un repositorio en la gran clase de hipotecas para resolver eso.

Además, no siempre es fácil encontrar buenos nombres para las clases que solo hacen una pequeña cosa.

Así que no estoy seguro de que la 'S' sea siempre una buena idea.

Michel
fuente