Aujourd’hui, j’ai pris part à une discussion sur la programmation au cours de laquelle j’ai fait des déclarations qui supposaient essentiellement que les références circulaires (entre modules, classes, peu importe) sont généralement mauvaises. Une fois ma présentation terminée, mon collègue m'a demandé: "Qu'est-ce qui ne va pas avec les références circulaires?"
J'ai de forts sentiments à ce sujet, mais il m'est difficile de verbaliser de manière concise et concrète. Toute explication que je pourrais trouver tend à s'appuyer sur d'autres éléments que je considère également comme axiomes ("ne peut pas être utilisé isolément, donc ne peut pas tester", "comportement inconnu / indéfini lorsque l'état passe aux objets participants", etc. .), mais j'aimerais entendre une raison concise des raisons pour lesquelles les références circulaires sont mauvaises et qui ne prend pas le genre de sauts de foi que mon propre cerveau fait, ayant passé de nombreuses heures au cours des années à les démêler pour comprendre, réparer, et étendre divers bits de code.
Edit: Je ne parle pas de références circulaires homogènes, comme celles d’une liste à double lien ou d’un pointeur sur parent. Cette question concerne en réalité les références circulaires de "portée plus large", comme libA appelant libB qui rappelle à libA. Remplacez 'module' par 'lib' si vous voulez. Merci pour toutes les réponses jusqu'à présent!
la source
Réponses:
Il y a beaucoup de problèmes avec les références circulaires:
Les références de classe circulaires créent un couplage élevé ; les deux classes doivent être recompilées chaque fois que l’ une d’elles est modifiée.
Les références d' assemblages circulaires empêchent la liaison statique , car B dépend de A mais A ne peut pas être assemblé tant que B n'est pas complet.
Les références d' objets circulaires peuvent bloquer les algorithmes récursifs naïfs (tels que les sérialiseurs, les visiteurs et les jolies imprimantes) avec des débordements de pile. Les algorithmes plus avancés auront une détection de cycle et échoueront simplement avec un message d'erreur / d'exception plus descriptif.
Les références d' objets circulaires rendent également l' injection de dépendance impossible , ce qui réduit considérablement la testabilité de votre système.
Les objets avec un très grand nombre de références circulaires sont souvent des objets divins . Même s'ils ne le sont pas, ils ont tendance à conduire au code spaghetti .
Les références d' entités circulaires (en particulier dans les bases de données, mais également dans les modèles de domaine) empêchent l'utilisation de contraintes de non-nullabilité , pouvant éventuellement conduire à une corruption des données ou au moins à une incohérence.
Les références circulaires en général sont simplement déroutantes et augmentent considérablement la charge cognitive lorsqu'on tente de comprendre le fonctionnement d'un programme.
S'il vous plaît, pensez aux enfants; Évitez les références circulaires chaque fois que vous le pouvez.
la source
Une référence circulaire est le double du couplage d'une référence non circulaire.
Si Foo est au courant de l'existence de Bar et que Bar est au courant de Foo, vous avez deux choses à changer (lorsque vient le temps que Foos et Bars ne doivent plus se connaître). Si Foo est au courant de l'existence de Bar, mais qu'un bar n'en a pas connaissance, vous pouvez changer de Foo sans toucher Bar.
Les références cycliques peuvent également causer des problèmes d’amorçage, du moins dans des environnements qui durent longtemps (services déployés, environnements de développement basés sur une image), où Foo dépend du travail de Bar pour se charger, mais également du travail de Foo afin de charge.
la source
Lorsque vous associez deux bouts de code, vous avez effectivement un gros morceau de code. La difficulté de conserver un peu de code est au moins le carré de sa taille, voire plus.
Les gens regardent souvent la complexité d'une classe (/ fonction / fichier / etc.) et oublient que vous devriez vraiment considérer la complexité de la plus petite unité séparable (pouvant être encapsulée). Avoir une dépendance circulaire augmente la taille de cette unité, éventuellement de manière invisible (jusqu'à ce que vous commenciez à essayer de modifier le fichier 1 et à vous rendre compte que cela nécessite également des modifications dans les fichiers 2 à 127).
la source
Ils peuvent être mauvais non pas en eux-mêmes mais en tant qu’indicateur d’une mauvaise conception possible. Si Foo dépend de Bar et Bar dépend de Foo, il est justifié de se demander pourquoi ils sont deux au lieu d'un FooBar unique.
la source
Hmm ... cela dépend de ce que vous entendez par dépendance circulaire, car il existe en fait des dépendances circulaires qui, à mon avis, sont très bénéfiques.
Considérons un DOM XML - il est logique que chaque nœud ait une référence à son parent et que chaque parent ait une liste de ses enfants. La structure est logiquement un arbre, mais du point de vue d'un algorithme de récupération de place ou similaire, la structure est circulaire.
la source
Node
classe comporte d'autres référencesNode
pour les enfants. Comme elle ne fait que se référencer, la classe est complètement autonome et n'est couplée à rien d'autre. --- Avec cet argument, vous pourriez soutenir qu'une fonction récursive est une dépendance circulaire. Il est (à un étirement), mais pas une mauvaise façon.Est comme le problème du poulet ou de l'oeuf .
Il existe de nombreux cas dans lesquels les références circulaires sont inévitables et utiles, mais cela ne fonctionne pas dans le cas suivant:
Le projet A dépend du projet B et B dépend de A. A doit être compilé pour pouvoir être utilisé dans B, ce qui nécessite que B soit compilé avant A, ce qui nécessite que B soit compilé avant A qui ...
la source
Bien que je sois d’accord avec la plupart des commentaires ici, je voudrais plaider un cas spécial pour la référence circulaire "parent" / "enfant".
Une classe a souvent besoin de savoir quelque chose sur sa classe mère ou propriétaire, peut-être son comportement par défaut, le nom du fichier d'où proviennent les données, l'instruction SQL qui a sélectionné la colonne ou l'emplacement d'un fichier journal, etc.
Vous pouvez le faire sans référence circulaire en ayant une classe contenante de sorte que ce qui était auparavant le "parent" soit maintenant un frère, mais il n'est pas toujours possible de re-factoriser le code existant pour le faire.
L’autre solution consiste à transmettre toutes les données dont un enfant peut avoir besoin dans son constructeur, qui sont tout simplement horribles.
la source
En termes de base de données, les références circulaires avec les relations PK / FK appropriées empêchent l’insertion ou la suppression de données. Si vous ne pouvez pas supprimer de la table a sauf si l'enregistrement est supprimé de la table b et si vous ne pouvez pas supprimer de la table b sauf si l'enregistrement est supprimé de la table A, vous ne pouvez pas supprimer. Même avec des inserts. C'est pourquoi de nombreuses bases de données ne vous permettent pas de configurer des mises à jour en cascade ou des suppressions s'il existe une référence circulaire car, à un moment donné, cela devient impossible. Oui, vous pouvez configurer ce type de relations sans que le PK / Fk soit officiellement déclaré, mais vous aurez alors (100% du temps, selon mon expérience) des problèmes d’intégrité des données. C'est juste une mauvaise conception.
la source
Je vais prendre cette question du point de vue de modélisation.
Tant que vous n'ajoutez pas de relations qui n'y sont pas, vous êtes en sécurité. Si vous les ajoutez, vous obtenez moins d'intégrité dans les données (car il y a une redondance) et un code plus étroitement couplé.
Le problème avec les références circulaires, en particulier, est que je n’ai pas vu de cas où elles seraient réellement nécessaires, sauf une - référence personnelle. Si vous modélisez des arbres ou des graphiques, vous en avez besoin et tout va bien, car la référence à soi est sans danger du point de vue de la qualité du code (aucune dépendance ajoutée).
Je pense qu'au moment où vous commencez à avoir besoin d'une référence non auto-référente, vous devez immédiatement demander si vous ne pouvez pas le modéliser sous forme de graphique (réduire les entités multiples en un seul nœud). Peut-être existe-t-il un cas entre deux références où vous faites une référence circulaire, mais la modélisation sous forme de graphique n'est pas appropriée, mais j'en doute fortement.
Les gens risquent de penser qu’ils ont besoin d’une référence circulaire, mais ce n’est pas le cas. Le cas le plus commun est "Le cas un-de-plusieurs". Par exemple, vous avez un client avec plusieurs adresses parmi lesquelles une doit être marquée comme adresse principale. Il est très tentant de modéliser cette situation sous la forme de deux relations distinctes has_address et is_primary_address_of mais ce n'est pas correct. La raison en est qu'être l'adresse principale n'est pas une relation séparée entre les utilisateurs et les adresses, mais bien un attribut de la relation à l' adresse. Pourquoi donc? Parce que son domaine est limité aux adresses de l'utilisateur et non à toutes les adresses existantes. Vous choisissez l'un des liens et le marquez comme le plus fort (primaire).
(Parlons maintenant des bases de données) Beaucoup de gens optent pour la solution à deux relations parce qu'ils comprennent que «primaire» est un pointeur unique et qu'une clé étrangère est en quelque sorte un pointeur. Donc, la clé étrangère devrait être la chose à utiliser, non? Faux. Les clés étrangères représentent des relations mais "primaire" n'est pas une relation. C'est un cas dégénéré de commande où un élément est avant tout et le reste n'est pas commandé. Si vous aviez besoin de modéliser un ordre total, vous le considéreriez bien sûr comme un attribut de relation, car il n'y a pas d'autre choix. Mais au moment où vous le dégénérez, il existe un choix et un choix horrible: modéliser quelque chose qui n'est pas une relation en tant que relation. Alors voilà, la redondance des relations n’est certainement pas à sous-estimer.
Donc, je ne laisserais pas une référence circulaire se produire à moins qu'il soit absolument clair que cela vient de la chose que je modélise.
(note: ceci est légèrement biaisé par la conception de la base de données mais je parierais que c'est assez applicable à d'autres domaines aussi)
la source
Je répondrais à cette question par une autre question:
Quelle situation pouvez-vous me donner où le maintien d'un modèle de référence circulaire est le meilleur modèle pour ce que vous essayez de construire?
D'après mon expérience, le meilleur modèle n'impliquera pratiquement jamais de références circulaires, comme je le pense. Cela étant dit, il existe de nombreux modèles dans lesquels vous utilisez des références circulaires à tout moment, c’est extrêmement simple. Relations parent -> enfant, tout modèle graphique, etc., mais ce sont des modèles bien connus et je pense que vous faites allusion à autre chose.
la source
Les références circulaires dans les structures de données constituent parfois le moyen naturel d’exprimer un modèle de données. En ce qui concerne le codage, ce n’est certainement pas l’idéal et peut être résolu (dans une certaine mesure) par l’injection de dépendances, faisant passer le problème du code aux données.
la source
Une construction de référence circulaire pose problème, non seulement du point de vue de la conception, mais également de la détection des erreurs.
Envisagez la possibilité d’une défaillance du code. Vous n'avez pas placé d'erreur appropriée dans les deux classes, soit parce que vous n'avez pas encore développé vos méthodes, soit parce que vous êtes paresseux. Dans les deux cas, vous n'avez pas de message d'erreur vous indiquant ce qui s'est passé et vous devez le déboguer. En tant que bon concepteur de programme, vous savez quelles méthodes sont associées à quels processus. Vous pouvez donc les limiter aux méthodes pertinentes pour le processus à l'origine de l'erreur.
Avec des références circulaires, vos problèmes ont maintenant doublé. Comme vos processus sont étroitement liés, vous n’avez aucun moyen de savoir quelle méthode dans quelle classe pourrait avoir causé l’erreur, ni d’où vient l’erreur, car une classe dépend de l’autre dépend de l’autre. Vous devez maintenant passer du temps à tester les deux classes en même temps pour déterminer lequel est réellement responsable de l'erreur.
Bien sûr, la détection correcte des erreurs résout ce problème, mais uniquement si vous savez quand une erreur risque de se produire. Et si vous utilisez des messages d'erreur génériques, vous n'êtes toujours pas mieux loti.
la source
Certains éboueurs ont du mal à les nettoyer, car chaque objet est référencé par un autre.
EDIT: Comme indiqué dans les commentaires ci-dessous, cela n’est vrai que pour une tentative extrêmement naïve de dépoussiéreur, qui n’est pas celle que vous rencontreriez dans la pratique.
la source
À mon avis, disposer de références illimitées facilite la conception de programmes, mais nous savons tous que certains langages de programmation ne les prennent pas en charge dans certains contextes.
Vous avez mentionné des références entre modules ou classes. Dans ce cas, c'est une chose statique, prédéfinie par le programmeur, et il est clairement possible pour le programmeur de rechercher une structure dépourvue de circularité, même si cela ne convient pas nécessairement au problème.
Le problème réel vient de la circularité dans les structures de données d'exécution, où certains problèmes ne peuvent en réalité pas être définis de manière à éliminer la circularité. En fin de compte, c’est le problème qui devrait dicter et imposer quoi que ce soit d’obliger le programmeur à résoudre un casse-tête inutile.
Je dirais que c'est un problème avec les outils, pas un problème avec le principe.
la source