web-dev-qa-db-ja.com

循環参照の何が問題になっていますか?

今日、プログラミングの議論に参加しましたが、基本的には(モジュール、クラス間の)循環参照は一般的に悪いと公理的に想定しているステートメントをいくつか作成しました。売り込みを終えると、同僚は「循環参照の何が問題になっていますか?」と尋ねました。

私はこれについて強い感情を持っていますが、簡潔かつ具体的に言葉で表現するのは難しいです。私が思いつく可能性のある説明は、私も公理と考えている他の項目に依存する傾向があります(「単独で使用できないため、テストできない」、「状態が関与するオブジェクトで変化するため、不明/未定義の動作」など。 。)、しかし、循環参照が悪い理由を簡潔に説明し、私の脳が行うような飛躍的な信念を持たない理由を聞きたいと思います。理解し、修正するために何年にもわたって何時間もそれらを解きほぐしました。コードのさまざまなビットを拡張します。

編集:二重にリンクされたリストや親へのポインターのような、同種の循環参照については質問していません。この質問は、libAがlibAをコールバックするlibBを呼び出すような「より大きなスコープ」の循環参照について本当に尋ねています。必要に応じて、「lib」を「module」に置き換えます。これまでのすべての回答をありがとう!

167
dash-tom-bang

循環参照には多くの多くの誤りがあります:

  • 循環クラス参照は高い結合を作成します。 両方クラスはどちらかが変更されるたびに再コンパイルする必要があります.

  • 循環アセンブリ参照静的リンクを防止です。これは、BがAに依存しているが、Bが完了するまでAをアセンブリできないためです。

  • 循環object参照は可能crashスタックオーバーフローを伴う単純な再帰アルゴリズム(シリアライザー、ビジター、プリティプリンターなど)。より高度なアルゴリズムにはサイクル検出機能があり、単に説明的な例外/エラーメッセージで失敗します。

  • 循環object参照も依存性注入を不可能にし、システムのtestabilityを大幅に削減します。

  • 非常にlargeの循環参照を持つオブジェクトは、しばしば God Objects です。そうでない場合でも、それらは スパゲッティコード につながる傾向があります。

  • 循環entity参照(特にデータベース、ドメインモデルでも)はnon-nullability制約の使用を防ぎます。これにより、最終的にデータの破損または少なくとも不整合が発生する可能性があります。

  • 循環参照一般には単純に混乱であり、プログラムがどのように機能するかを理解しようとすると、認知負荷が大幅に増加します。

子供たちのことを考えてください。できる限り循環参照は避けてください。

228
Aaronaught

循環参照は、非循環参照の2倍の結合です。

FooがBarを知っており、BarがFooを知っている場合、変更が必要なものが2つあります(FoosとBarsがお互いについてもう知ってはならないという要件が発生した場合)。 FooがBarを知っていても、BarがFooを知らない場合は、Barに触れずにFooを変更できます。

循環参照は、少なくとも長時間続く環境(デプロイされたサービス、イメージベースの開発環境)でブートストラップの問題を引き起こす可能性もあります。この場合、Fooはロードするために動作するBarに依存しますが、Barは動作するために動作するFooにも依存します負荷。

22
Frank Shearar

2ビットのコードを結合すると、事実上、1つの大きなコードができます。コードのビットを維持することの難しさは、少なくともそのサイズの2乗であり、おそらくそれよりも大きいです。

人々はしばしば単一のクラス(/関数/ファイルなど)の複雑さを見て、最小の分離可能な(カプセル化可能な)ユニットの複雑さを本当に考慮すべきであることを忘れます。循環依存関係があると、その単位のサイズが増加し、おそらく目に見えないほどになります(ファイル1を変更しようとして、ファイル2-127にも変更が必要になることに気付くまで)。

17
Alex Feinman

それらはそれ自体ではなく、デザインが悪い可能性を示す指標として悪いのかもしれません。 FooがBarに依存し、BarがFooに依存している場合、それらが一意のFooBarではなく2つである理由を質問することは正当化されます。

14
mouviciel

うーん...それはあなたが循環依存によって何を意味するかに依存します、なぜなら私が非常に有益だと思う循環依存が実際にあるからです。

XML DOMについて考えてみましょう。すべてのノードが親への参照を持ち、すべての親がその子のリストを持つことが理にかなっています。構造は論理的にはツリーですが、ガベージコレクションアルゴリズムなどの観点から見ると、構造は円形です。

10
Billy ONeal

鶏または卵 の問題のようなものです。

循環参照が避けられず便利な場合が多くありますが、たとえば、次の場合は機能しません。

プロジェクトAはプロジェクトBに依存しており、BはAに依存しています。

9

ここでのコメントのほとんどには同意しますが、「親」/「子」の循環参照についての特別なケースを認めたいと思います。

多くの場合、クラスは親クラスまたは所有クラスについて何かを知っている必要があります。おそらくデフォルトの動作、データの元となったファイルの名前、列を選択したSQLステートメント、またはログファイルの場所などです。

これを含むクラスを持つことで循環参照なしでこれを行うことができるため、以前は「親」であったものが兄弟になりますが、既存のコードをリファクタリングしてこれを行うことが常に可能なわけではありません。

もう1つの方法は、子がそのコンストラクタで必要とする可能性があるすべてのデータを渡すことです。

6
James Anderson

データベースの用語では、適切なPK/FK関係を持つ循環参照により、データの挿入または削除が不可能になります。レコードがテーブルbから削除されない限りテーブルaから削除できず、レコードがテーブルAから削除されない限りテーブルbから削除できない場合は、削除できません。インサートも同様です。これが循環参照がある場合、多くのデータベースでカスケード更新またはカスケード削除を設定できない理由です。ある時点でそれが不可能になるためです。はい、正式に宣言されているPK/Fkがなくても、このような関係を設定できますが、(私の経験では100%)データの整合性の問題が発生します。それは単に悪いデザインです。

5
HLGEM

モデリングの観点からこの質問を取り上げます。

実際に存在しない関係を追加しない限り、安全です。それらを追加すると、データの整合性が失われ(冗長性があるため)、コードが密結合されます。

特に循環参照の問題は、1つを除いて実際に必要になるケースを見たことがないということです-自己参照。ツリーまたはグラフをモデル化する場合、それが必要です。コード参照の観点から自己参照は無害であるため、依存関係は追加されないため、完全に問題ありません。

非自己参照が必要になり始めた瞬間に、それをグラフとしてモデル化できないかどうか(複数のエンティティを1つのノードに折りたたむ)かどうかをすぐに確認する必要があると思います。循環参照をする場合があるかもしれませんが、それをグラフとしてモデル化することは適切ではありませんが、私はそれを強く疑います。

循環参照が必要だと人々が考える危険性がありますが、実際には必要ありません。最も一般的なケースは、「多数のケース」です。たとえば、複数の住所を持つ顧客がいて、そのうちの1つを主要住所としてマークする必要があるとします。この状況を2つの個別の関係has_addressおよびis_primary_address_ofとしてモデル化するのは非常に魅力的です)しかし、それは正しくありません。その理由は、プライマリアドレスであることはユーザーとアドレスの間の独立した関係ではなく、an attribute関係にはアドレスがあります。何故ですか?ドメインはユーザーのアドレスに限定されており、すべてのアドレスに限定されているわけではないためです。リンクの1つを選択し、それを最強(プライマリ)としてマークします。

(データベースについて話します)多くの人々は、「プライマリ」が一意のポインタであり、外部キーが一種のポインタであると理解しているため、2関係ソリューションを選択します。だから、外部キーを使うべきですよね?違う。外部キーは関係を表しますが、「主」は関係ではありません。これは、1つの要素が何よりも優先され、残りの要素が順序付けられない順序付けの縮退したケースです。全体の順序をモデル化する必要がある場合は、基本的に他に選択肢がないため、もちろんそれを関係の属性と見なします。しかし、あなたがそれを縮退させた瞬間には、関係ではない何かを関係としてモデル化するという選択肢があり、かなり恐ろしいものがあります。だから、ここに来る-確かに過小評価されるものではない関係の冗長性。一意性の要件は、たとえば一意の部分インデックスなど、別の方法で課す必要があります。

したがって、私がモデル化しているものからのものであることが絶対に明らかでない限り、循環参照が発生することを許可しません。

(注:これはデータベース設計に若干偏っていますが、他の領域にもかなり適用できると思います)

4
clime

私は別の質問でその質問に答えます:

循環参照モデルを維持することが、構築しようとしているもののbestモデルである場合、どのような状況が考えられますか?

私の経験から、最良のモデルは、私が言うところの循環参照をほとんど含まないでしょう。とはいえ、常に循環参照を使用するモデルはたくさんありますが、それは非常に基本的なものです。親->子関係、任意のグラフモデルなどですが、これらはよく知られたモデルであり、完全に別のものを参照していると思います。

2
Joseph

一部のガベージコレクターは、各オブジェクトが別のオブジェクトによって参照されているため、クリーンアップに問題があります。

編集:以下のコメントで述べられているように、これはガベージコレクターでの極端に素朴な試みにのみ当てはまり、実際に遭遇することはありません。

1
shmuelp

循環参照構成は、設計の観点からだけでなく、エラーをキャッチする観点からも問題があります。

コード障害の可能性を考慮してください。メソッドをまだ開発していないか、怠惰なため、どちらのクラスにも適切なエラーキャッチを配置していません。どちらの場合も、何が発生したかを示すエラーメッセージは表示されず、デバッグする必要があります。優れたプログラム設計者として、どのメソッドがどのプロセスに関連しているかがわかるので、エラーの原因となったプロセスに関連するメソッドに絞り込むことができます。

循環参照により、問題が2倍になりました。プロセスは緊密にバインドされているため、どのクラスのどのメソッドがエラーを引き起こしたのか、またはどこからエラーが発生したのかを知る方法がありません。1つのクラスが他のクラスに依存しているためです。ここで、両方のクラスを組み合わせてテストすることに時間を費やして、どちらが本当にエラーの原因であるかを見つける必要があります。

もちろん、適切なエラーキャッチはこれを解決しますが、エラーが発生する可能性が高い時期がわかっている場合のみです。また、一般的なエラーメッセージを使用している場合でも、それほど良い結果は得られません。

1
Zibbobz

データ構造の循環参照は、データモデルを表現する自然な方法です。コーディングに関しては、これは明らかに理想的ではなく、依存性注入によって(ある程度)問題をコードからデータにプッシュすることで解決できます。

1
Vatine