¿Qué hay de malo con las referencias circulares?

144

Estuve involucrado en una discusión de programación hoy, donde hice algunas afirmaciones que, básicamente, asumían axiomáticamente que las referencias circulares (entre módulos, clases, lo que sea) son generalmente malas. Una vez que terminé con mi presentación, mi compañero de trabajo preguntó: "¿qué hay de malo con las referencias circulares?"

Tengo fuertes sentimientos sobre esto, pero es difícil para mí verbalizar de manera concisa y concreta. Cualquier explicación que se me ocurra tiende a depender de otros elementos que también considero axiomas ("no se puede usar de forma aislada, por lo que no se puede probar", "comportamiento desconocido / indefinido ya que el estado muta en los objetos participantes", etc. .), pero me encantaría escuchar una razón concisa de por qué las referencias circulares son malas y no dan el tipo de saltos de fe que hace mi propio cerebro, después de haber pasado muchas horas a lo largo de los años desentrañándolas para comprenderlas, corregirlas, y extender varios bits de código.

Editar: No estoy preguntando por referencias circulares homogéneas, como las de una lista con doble enlace o puntero a padre. Esta pregunta es realmente acerca de referencias circulares de "mayor alcance", como libA llamando a libB, que devuelve la llamada a libA. Sustituya 'módulo' por 'lib' si lo desea. ¡Gracias por todas las respuestas hasta ahora!

    
pregunta dash-tom-bang 14.10.2010 - 02:01
fuente

14 respuestas

202

Hay un gran número de muchos errores con las referencias circulares:

  • Circular clase las referencias crean un alto acoplamiento ; ambas clases deben recompilarse cada vez que se modifique cualquiera de ellas .

  • El ensamblaje circular hace referencia a evitar el enlace estático , porque B depende de A pero A no se puede ensamblar hasta que B se complete.

  • El objeto circular las referencias pueden bloquear los algoritmos recursivos ingenuos (como serializadores, visitantes e impresoras bonitas) con desbordamientos de pila. Los algoritmos más avanzados tendrán detección de ciclos y simplemente fallarán con un mensaje de error / excepción más descriptivo.

  • El objeto circular también hace que la inyección de dependencia sea imposible , lo que reduce significativamente la capacidad de prueba de su sistema.

  • Los objetos con un número muy grande de referencias circulares son a menudo Objetos de Dios . Incluso si no lo son, tienen una tendencia a llevar a Spaghetti Code .

  • Circular entidad las referencias (especialmente en las bases de datos, pero también en los modelos de dominio) impiden el uso de restricciones de no anulabilidad , lo que puede llevar a la corrupción de datos. o al menos inconsistencia.

  • Las referencias circulares en general son simplemente confusas y aumentan drásticamente la carga cognitiva al intentar comprender cómo funciona un programa.

Por favor, piensa en los niños; Evita las referencias circulares siempre que puedas.

    
respondido por el Aaronaught 14.10.2010 - 18:06
fuente
20

Una referencia circular es el doble del acoplamiento de una referencia no circular.

Si Foo sabe de Bar, y Bar sabe de Foo, tienes dos cosas que debes cambiar (cuando llega el requisito de que Foos y Bars ya no se conozcan). Si Foo sabe sobre Bar, pero un Bar no sabe sobre Foo, puedes cambiar Foo sin tocar Bar.

Las referencias cíclicas también pueden causar problemas de arranque, al menos en entornos que duran mucho tiempo (servicios implementados, entornos de desarrollo basados en imágenes), donde Foo depende del funcionamiento de la barra para cargar, pero la barra también depende del funcionamiento de Foo para cargar.

    
respondido por el Frank Shearar 14.10.2010 - 08:00
fuente
16

Cuando unes dos bits de código, efectivamente tienes un gran fragmento de código. La dificultad de mantener un poco de código es al menos el cuadrado de su tamaño, y posiblemente sea mayor.

La gente a menudo ve la complejidad de una sola clase (/ function / file / etc.) y olvida que realmente debería considerar la complejidad de la unidad separable (encapsulable) más pequeña. Tener una dependencia circular aumenta el tamaño de esa unidad, posiblemente de manera invisible (hasta que empiece a intentar cambiar el archivo 1 y se dé cuenta de que también requiere cambios en los archivos 2-127).

    
respondido por el Alex Feinman 14.10.2010 - 15:39
fuente
12

Pueden no ser malos por sí mismos, sino como un indicador de un posible mal diseño. Si Foo depende de Bar y Bar depende de Foo, está justificado cuestionar por qué son dos en lugar de un FooBar único.

    
respondido por el mouviciel 14.10.2010 - 10:19
fuente
10

Hmm ... eso depende de lo que quieres decir con dependencia circular, porque en realidad hay algunas dependencias circulares que creo que son muy beneficiosas.

Considere un DOM XML: tiene sentido que cada nodo tenga una referencia a su padre y que cada padre tenga una lista de sus hijos. La estructura es lógicamente un árbol, pero desde el punto de vista de un algoritmo de recolección de basura o similar, la estructura es circular.

    
respondido por el Billy ONeal 14.10.2010 - 02:39
fuente
9

Es como el problema Chicken or the Egg .

Hay muchos casos en los que la referencia circular es inevitable y es útil, pero, por ejemplo, en el siguiente caso no funciona:

El proyecto A depende del proyecto B y B depende de A. A debe compilarse para usarse en B, que requiere que B se compile antes de A, lo que requiere que B se compile antes de A, que ...

    
respondido por el Victor Hurdugaci 14.10.2010 - 10:23
fuente
6

Aunque estoy de acuerdo con la mayoría de los comentarios aquí, me gustaría defender un caso especial para la referencia circular "padre" / "hijo".

Una clase a menudo necesita saber algo sobre su padre o su clase propietaria, tal vez el comportamiento predeterminado, el nombre del archivo del que provienen los datos, la declaración SQL que seleccionó la columna o la ubicación de un archivo de registro, etc.

Puede hacer esto sin una referencia circular si tiene una clase contenedora, de modo que lo que antes era el "padre" ahora es un hermano, pero no siempre es posible modificar el código existente para hacer esto.

La otra alternativa es pasar todos los datos que un niño podría necesitar en su constructor, que terminan siendo simplemente horribles.

    
respondido por el James Anderson 26.11.2013 - 08:33
fuente
5

En términos de la base de datos, las referencias circulares con relaciones PK / FK adecuadas hacen que sea imposible insertar o eliminar datos. Si no puede eliminar de la tabla a a menos que el registro haya desaparecido de la tabla b y no puede eliminar de la tabla b a menos que el registro haya desaparecido de la tabla A, no puede eliminar. Lo mismo con inserciones. esta es la razón por la que muchas bases de datos no le permiten configurar actualizaciones en cascada o eliminaciones si hay una referencia circular porque en algún momento ya no es posible. Sí, puede establecer este tipo de relaciones sin que se declaren formalmente los PK / Fk pero luego (el 100% del tiempo en mi experiencia) tendrá problemas de integridad de datos. Eso es un mal diseño.

    
respondido por el HLGEM 14.10.2010 - 16:34
fuente
3

Tomaré esta pregunta desde el punto de vista del modelado.

Mientras no agregues ninguna relación que no esté realmente allí, estás a salvo. Si los agrega, obtendrá menos integridad en los datos (porque existe una redundancia) y un código más estrechamente acoplado.

El problema con las referencias circulares específicamente es que no he visto un caso en el que realmente se necesiten, excepto una: la propia referencia. Si modela árboles o gráficos, necesita eso y está perfectamente bien porque la auto-referencia es inofensiva desde el punto de vista de la calidad del código (no se agrega dependencia).

Creo que en el momento en que comienza a necesitar una referencia no automática, inmediatamente debe preguntar si no puede modelarla como un gráfico (contraiga las múltiples entidades en un nodo). Quizás haya un caso intermedio en el que se haga una referencia circular, pero modelarlo como gráfico no es apropiado, pero lo dudo mucho.

Existe el peligro de que las personas piensen que necesitan una referencia circular, pero en realidad no lo hacen. El caso más común es "El caso de uno de muchos". Por ejemplo, tiene un cliente con varias direcciones, de las cuales una debe estar marcada como la dirección principal. Es muy tentador modelar esta situación como dos relaciones separadas has_address y is_primary_address_of pero no es correcta. La razón es que es la dirección principal no es una relación separada entre los usuarios y las direcciones, sino que es un atributo de la relación tiene la dirección . ¿Porqué es eso? Debido a que su dominio está limitado a las direcciones del usuario y no a todas las direcciones que hay. Selecciona uno de los enlaces y lo marca como el más fuerte (principal).

(Voy a hablar sobre bases de datos ahora) Muchas personas optan por la solución de dos relaciones porque entienden que "primario" es un puntero único y que una clave externa es un tipo de puntero. Así que la clave externa debería ser la cosa a usar, ¿verdad? Incorrecto. Las claves externas representan relaciones, pero "primaria" no es una relación. Es un caso degenerado de una ordenación donde un elemento está por encima de todo y el resto no está ordenado. Si necesitara modelar un pedido total, por supuesto lo consideraría como un atributo de la relación porque básicamente no hay otra opción. Pero en el momento en que lo degeneras, hay una opción bastante horrible: modelar algo que no es una relación como una relación. De modo que aquí viene la redundancia de relaciones, que ciertamente no es algo que deba subestimarse. El requisito de unicidad debe imponerse de otra manera, por ejemplo, mediante índices parciales únicos.

Por lo tanto, no permitiría que se produjera una referencia circular a menos que esté absolutamente claro que proviene de lo que estoy modelando.

(nota: esto está ligeramente orientado al diseño de la base de datos, pero apostaría a que es bastante aplicable también a otras áreas)

    
respondido por el clime 29.11.2013 - 04:02
fuente
2

Yo contestaría esa pregunta con otra pregunta:

¿Qué situación me puede dar si mantener un modelo de referencia circular es el modelo mejor para lo que está intentando construir?

Desde mi experiencia, el mejor modelo casi nunca involucrará referencias circulares de la manera que creo que lo dices. Dicho esto, hay muchos modelos en los que se usan referencias circulares todo el tiempo, es extremadamente básico. Padre - > Relaciones con niños, cualquier modelo gráfico, etc., pero estos son modelos bien conocidos y creo que te estás refiriendo a algo completamente distinto.

    
respondido por el Joseph 14.10.2010 - 02:39
fuente
1

Las referencias circulares en las estructuras de datos son a veces la forma natural de expresar un modelo de datos. En lo que respecta a la codificación, definitivamente no es lo ideal y puede resolverse (hasta cierto punto) mediante la inyección de dependencia, empujando el problema de un código a otro.

    
respondido por el Vatine 29.11.2013 - 11:34
fuente
1

Una construcción de referencia circular es problemática, no solo desde el punto de vista del diseño, sino también desde el punto de vista de la captura de errores.

Considere la posibilidad de una falla de código. No ha colocado la captura correcta de errores en ninguna de las clases, ya sea porque todavía no ha desarrollado sus métodos o es perezoso. De cualquier manera, no tienes un mensaje de error que te diga lo que ocurrió y necesitas depurarlo. Como buen diseñador de programas, usted sabe qué métodos están relacionados con qué procesos, por lo que puede limitarlos a los métodos pertinentes al proceso que causó el error.

Con referencias circulares, tus problemas ahora se han duplicado. Debido a que sus procesos están estrechamente vinculados, no tiene forma de saber qué método en qué clase podría haber causado el error, o de dónde vino el error, porque una clase depende de la otra depende de la otra. Ahora tiene que dedicar tiempo a probar ambas clases en conjunto para descubrir cuál es realmente responsable del error.

Por supuesto, la captura correcta de errores resuelve esto, pero solo si sabe cuándo es probable que ocurra un error. Y si está utilizando mensajes de error genéricos, todavía no está mucho mejor.

    
respondido por el Zibbobz 29.05.2015 - 16:23
fuente
1

Algunos recolectores de basura tienen problemas para limpiarlos, ya que cada objeto hace referencia a otro.

EDITAR: Como se señala en los comentarios a continuación, esto es cierto solo para un intento ingenuo de extremadamente de un recolector de basura, no uno que puedas encontrar en la práctica.

    
respondido por el shmuelp 14.10.2010 - 02:36
fuente
-2

En mi opinión, tener referencias sin restricciones facilita el diseño del programa, pero todos sabemos que algunos lenguajes de programación carecen de soporte para ellos en algunos contextos.

Mencionaste referencias entre módulos o clases. En ese caso, es una cosa estática, predefinida por el programador, y es claramente posible que el programador busque una estructura que carezca de circularidad, aunque podría no ajustarse al problema de manera limpia.

El problema real se presenta en la circularidad en las estructuras de datos en tiempo de ejecución, donde algunos problemas en realidad no se pueden definir de una manera que elimine la circularidad. Sin embargo, al final, el problema que debe dictar y exigir cualquier otra cosa es obligar al programador a resolver un problema innecesario.

Yo diría que es un problema con las herramientas, no un problema con el principio.

    
respondido por el Josh S 11.05.2014 - 03:23
fuente

Lea otras preguntas en las etiquetas