Para mí es un problema de acoplamiento y está relacionado con la granularidad del diseño. Incluso la forma más flexible de acoplamiento introduce dependencias de una cosa a otra. Si eso se hace entre cientos y miles de objetos, incluso si todos son relativamente simples, adhiérase a SRP, e incluso si todas las dependencias fluyen hacia abstracciones estables, eso produce una base de código que es muy difícil de razonar como un todo interrelacionado.
Hay cosas prácticas que lo ayudan a medir la complejidad de una base de código, que no se discute con frecuencia en la SE teórica, como qué tan profundo puede llegar a la pila de llamadas antes de llegar al final y qué tan profundo debe llegar antes de poder hacerlo, con mucha confianza, entienda todos los posibles efectos secundarios que podrían ocurrir en ese nivel de la pila de llamadas, incluso en el caso de una excepción.
Y descubrí, solo en mi experiencia, que los sistemas más planos con pilas de llamadas menos profundas tienden a ser mucho más fáciles de razonar. Un ejemplo extremo sería un sistema de entidad-componente donde los componentes son solo datos sin procesar. Solo los sistemas tienen funcionalidad, y en el proceso de implementación y uso de un ECS, encontré que es el sistema más fácil, hasta ahora, para razonar sobre cuándo las bases de código complejas que abarcan cientos de miles de líneas de código básicamente se reducen a unas pocas docenas de sistemas que Contiene toda la funcionalidad.
Demasiadas cosas proporcionan funcionalidad
La alternativa anterior cuando trabajaba en bases de códigos anteriores era un sistema con cientos a miles de objetos en su mayoría pequeños, en lugar de unas pocas docenas de sistemas voluminosos con algunos objetos utilizados solo para pasar mensajes de un objeto a otro ( Message
objeto, por ejemplo, que tenía su interfaz pública propia). Eso es básicamente lo que obtienes de forma analógica cuando reviertes el ECS a un punto donde los componentes tienen funcionalidad y cada combinación única de componentes en una entidad produce su propio tipo de objeto. Y eso tenderá a producir funciones más pequeñas y simples heredadas y proporcionadas por infinitas combinaciones de objetos que modelan ideas juveniles ( Particle
objeto vs.Physics System
, p.ej). Sin embargo, también tiende a generar un gráfico complejo de interdependencias que dificulta razonar sobre lo que sucede desde el nivel amplio, simplemente porque hay muchas cosas en la base de código que realmente pueden hacer algo y, por lo tanto, pueden hacer algo mal. - tipos que no son tipos de "datos", sino tipos de "objeto" con funcionalidad asociada. Los tipos que sirven como datos puros sin funcionalidad asociada no pueden salir mal ya que no pueden hacer nada por sí mismos.
Las interfaces puras no ayudan mucho a este problema de comprensión porque, incluso si eso hace que las "dependencias de tiempo de compilación" sean menos complicadas y proporcione más espacio para respirar para el cambio y la expansión, no hace que las "dependencias de tiempo de ejecución" y las interacciones sean menos complicadas. El objeto del cliente aún termina invocando funciones en un objeto de cuenta concreto, incluso si se están llamando IAccount
. El polimorfismo y las interfaces abstractas tienen sus usos, pero no desacoplan las cosas de la manera que realmente te ayuda a razonar sobre todos los efectos secundarios que podrían ocurrir en un punto dado. Para lograr este tipo de desacoplamiento efectivo, necesita una base de código que tenga muchas menos cosas que contengan funcionalidad.
Más datos, menos funcionalidad
Por lo tanto, he encontrado que el enfoque ECS, incluso si no lo aplica completamente, es extremadamente útil, ya que convierte lo que habrían sido cientos de objetos en datos sin procesar con sistemas voluminosos, más gruesos, que proporcionan todos los funcionalidad Maximiza el número de tipos de "datos" y minimiza el número de tipos de "objetos" y, por lo tanto, minimiza absolutamente el número de lugares en su sistema que realmente pueden salir mal. El resultado final es un sistema muy "plano" sin un gráfico complejo de dependencias, solo sistemas a componentes, nunca al revés, y nunca componentes a otros componentes. Básicamente, son muchos más datos sin procesar y muchas menos abstracciones lo que tiene el efecto de centralizar y aplanar la funcionalidad de la base de código a áreas clave, abstracciones clave.
30 cosas más simples no son necesariamente más simples de razonar sobre 1 cosa más compleja, si esas 30 cosas más simples están interrelacionadas mientras la cosa compleja se sostiene por sí misma. Entonces, mi sugerencia es en realidad transferir la complejidad de las interacciones entre objetos y más hacia objetos más voluminosos que no tienen que interactuar con nada más para lograr el desacoplamiento masivo, a "sistemas" completos (no monolitos y objetos divinos, fíjate, y no clases con 200 métodos, sino algo considerablemente más alto que un Message
o un Particle
a pesar de tener una interfaz minimalista). Y favorezca tipos de datos antiguos más simples. Cuanto más dependas de ellos, menos acoplamiento tendrás. Incluso si eso contradice algunas ideas de SE, he descubierto que realmente ayuda mucho.