Un buen desarrollador con el que trabajo me contó recientemente sobre algunas dificultades que tenía para implementar una función en algún código que habíamos heredado; Dijo que el problema era que el código era difícil de seguir. A partir de eso, miré más profundamente en el producto y me di cuenta de lo difícil que era ver la ruta del código.
Usó tantas interfaces y capas abstractas, que tratar de entender dónde comenzaron y terminaron las cosas fue bastante difícil. Me hizo pensar en las veces que había visto proyectos anteriores (antes de que fuera tan consciente de los principios de código limpio) y me resultó extremadamente difícil avanzar en el proyecto, principalmente porque mis herramientas de navegación de código siempre me llevaban a una interfaz. Se necesitaría mucho esfuerzo adicional para encontrar la implementación concreta o dónde algo estaba conectado en alguna arquitectura de tipo de complemento.
Sé que algunos desarrolladores rechazan estrictamente los contenedores de inyección de dependencia por esta misma razón. Confunde tanto la ruta del software que la dificultad de la navegación de código aumenta exponencialmente.
Mi pregunta es: cuando un marco o patrón introduce tanta sobrecarga como esta, ¿vale la pena? ¿Es un síntoma de un patrón mal implementado?
Supongo que un desarrollador debería mirar la imagen más amplia de lo que esas abstracciones aportan al proyecto para ayudarlo a superar la frustración. Por lo general, sin embargo, es difícil hacer que vean ese panorama general. Sé que no pude vender las necesidades de COI y DI con TDD. Para esos desarrolladores, el uso de esas herramientas simplemente obstaculiza demasiado la legibilidad del código.
fuente
Bueno, no hay suficiente abstracción y su código es difícil de entender porque no puede aislar qué partes hacen qué.
Demasiada abstracción y ves la abstracción pero no el código en sí mismo, y luego hace que sea difícil seguir el hilo de ejecución real.
Para lograr una buena abstracción, uno debe BESAR: vea mi respuesta a estas preguntas para saber qué seguir para evitar ese tipo de problemas .
Creo que evitar la jerarquía profunda y los nombres son el punto más importante a tener en cuenta para el caso que describe. Si las abstracciones estuvieran bien nombradas, no tendría que profundizar demasiado, solo al nivel de abstracción donde necesita comprender lo que sucede. Los nombres le permiten identificar dónde está este nivel de abstracción.
El problema surge en el código de bajo nivel, cuando realmente necesita que se entienda todo el proceso. Entonces, la encapsulación a través de módulos claramente aislados es la única ayuda.
fuente
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 unParticle
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.fuente
Tal vez sea un síntoma de elegir el lenguaje de programación incorrecto.
fuente
La mala comprensión de los patrones de diseño tiende a ser una causa importante de este problema. Uno de los peores que he visto para este yo-yo'ing y rebotando de una interfaz a otra sin muchos datos concretos en el medio fue una extensión para el Control de cuadrícula de Oracle.
Sinceramente, parecía que alguien había tenido un método de fábrica abstracto y un orgasmo de patrón de decorador en todo mi código Java. Y me dejó sintiéndome tan vacía y sola.
fuente
También advertiría contra el uso de las funciones IDE que facilitan el resumen de cosas.
fuente