Dairesel referanslarda yanlış olan ne?


160

Bugün bir programlama tartışmasına katıldım, burada temelde aksiyomatik olarak dairesel referansların (modüller, sınıflar arasında, her neyse) kötü olduğunu varsayan bazı açıklamalar yaptım. Bir adım attığımda iş arkadaşım "Dairesel referansların nesi var?" Diye sordu.

Bu konuda güçlü hislerim var, ama net ve somut bir şekilde sözlü olarak söylemem zor. Karşılaştığım herhangi bir açıklama, aksiyomları da göz önünde bulundurduğum diğer öğelere dayanma eğilimindedir ("izolasyonda kullanılamaz, bu yüzden katılımcı nesnelerde devlet mutasyonları olarak bilinmeyen / tanımsız davranışı test edemez", vb.). .) ancak dairesel referansların kötü olmasının özünde, kendi beynimin yaptığı inanç sıçramalarını almayan, yıllarca birkaç saat boyunca onları anlamak, düzeltmek için onları çözmekle harcayan inanç sıçramalarını almayan bir neden duymak isterdim. ve çeşitli kod bitlerini genişletir.

Düzenleme: İki kat bağlantılı bir listedeki veya işaretçi-üstündeki gibi homojen dairesel referanslar hakkında soru sormuyorum. Bu soru gerçekten "daha geniş kapsamlı" döngüsel referanslar hakkında soruyor, libA'yı geri çağırmak gibi libA'yı çağırmak gibi libA'yı geri çağırmak gibi. İsterseniz 'lib' için 'modül' yerine. Şimdiye kadarki cevapların tümü için teşekkürler!


Dairesel referans, kitaplıklar ve başlık dosyaları için geçerli midir? Bir iş akışında, yeni ProjectB kodu eski ProjectA kodundan çıkan bir dosyayı işliyor olacak. ProjectA'dan gelen çıktı, ProjectB tarafından yönlendirilen yeni bir gereksinimdir; ProjectB vb varlık noktası, eski ProjectA olabilir nereye alanlar genel olarak belirleme kolaylaştıran bir kodu vardır kodu yeniden yeni ProjectB içinde ve ProjectB ProjectA (örneğin, eski utility kodu yeniden için saçma olur: karakter kümesi algılama ve kod çevrimi kayıt ayrıştırma, veri doğrulama ve dönüştürme vb.)
Luv2code

1
@ Luv2code Yalnızca projeler arasında kodu kesip yapıştırdığınızda veya muhtemelen her iki proje de aynı kodu derlediğinde ve bağladığınızda aptal hale gelir. Eğer böyle kaynakları paylaşıyorlarsa, onları bir kütüphaneye yerleştirin.
çizgi-tom-bang,

Yanıtlar:


220

Büyük vardır birçok dairesel referanslarla yanlış şeyler:

  • Dairesel sınıf referansları yüksek bağlanma yaratır ; her iki sınıf da , her biri değiştirildiği zaman yeniden derlenmelidir .

  • Dairesel montaj referansları statik bağlanmayı önler , çünkü B A'ya bağlıdır, ancak B tamamlanıncaya kadar A birleştirilemez.

  • Dairesel bir amacı referanslar olabilir çökmesine yığın taşmaları ile (örneğin, serileştiricileri, ziyaretçi ve hoş-yazıcılar gibi) saf yinelemeli algoritmalar. Daha gelişmiş algoritmalar döngü algılamaya sahip olacak ve sadece daha açıklayıcı bir istisna / hata mesajı ile başarısız olacaktır.

  • Dairesel nesne başvuruları ayrıca bağımlılık enjeksiyonunu imkansız hale getirir ve sisteminizin test edilebilirliğini önemli ölçüde azaltır .

  • Çok ile Nesneler büyük dairesel referansların sayısı genellikle Tanrı Nesneler . Olmasalar bile, Spagetti Yasasına yol açma eğilimindedirler .

  • Dairesel varlık başvuruları (özellikle veritabanlarında, aynı zamanda etki alanı modellerinde) , sonuçta verilerin bozulmasına ya da en azından tutarsızlığa yol açabilecek olan geçersizlik kısıtlamalarının kullanılmasını önler .

  • Genel olarak dairesel referanslar , bir programın nasıl çalıştığını anlamaya çalışırken bilişsel yükü basitçe karıştırır ve arttırır.

Lütfen çocukları düşünün; Yapabildiğiniz zaman dairesel referanslardan kaçının.


32
Son noktayı özellikle takdir ediyorum, "bilişsel yük" benim çok bilinçli olduğum, ancak bunun için çok kısa ve öz bir terim olmadığı bir şey.
dash-tom-bang

6
İyi cevap. Test hakkında bir şey söyleseydin daha iyi olurdu. A ve B modülleri karşılıklı olarak bağımlıysa, birlikte test edilmelidirler. Bu, gerçekten ayrı modüller olmadığı anlamına gelir; birlikte onlar tek bir kırık modül.
kevin cline

5
Otomatik DI bile olsa, dairesel referanslarda bağımlılık enjeksiyonu mümkün değildir. Bir yapıcı parametresi yerine sadece bir özellik enjekte edilmelidir.
BlueRaja - Danny Pflughoeft

3
@ BlueRaja-DannyPflughoeft: Diğer birçok DI uygulayıcısı gibi bir anti-patern olduğunu düşünüyorum, çünkü (a) bir mülkün aslında bir bağımlılık olduğu açık değildir ve (b) "enjekte edilen" nesnenin kolayca yapamayacağı açıktır. kendi değişmezlerini takip et. Daha kötüsü, Castle Windsor gibi en karmaşık / popüler çerçevelerin çoğu, bir bağımlılığın çözülememesi durumunda yararlı hata mesajları veremez; hangi yapının çözülemeyeceğine dair tam bir açıklama yapmak yerine can sıkıcı bir boş referans ile bitirdiniz. Sadece yapabildiğin için yapman gerektiği anlamına gelmez .
Aaron,

3
İyi bir uygulama olduğunu iddia etmiyordum, sadece cevabında iddia edildiği gibi imkansız olmadığını işaret ediyordum.
BlueRaja - Danny Pflughoeft,

22

Dairesel referans, dairesel olmayan bir referansın birleştirilmesinin iki katıdır.

Foo, Bar'ı biliyorsa ve Bar, Foo'yu biliyorsa, değiştirilmesi gereken iki şey vardır (gerek duyulduğunda, Foos ve Barların artık birbirlerini bilmemesi gerekir). Foo, Bar'ı biliyorsa, ancak bir Bar, Foo'yu bilmiyorsa, Foo'yu Bar'a dokunmadan değiştirebilirsiniz.

Döngüsel referanslar, en azından uzun süre dayanan ortamlarda (konuşlandırılmış hizmetler, görüntü tabanlı geliştirme ortamları), Foo'nun yüklenmek üzere Bar'ın çalışmasına bağlı olduğu, ancak Bar'ın da Foo'nun çalışabilmesi için çalıştığı ortamlarda olduğu gibi önyükleme sorunlarına da neden olabilir. yük.


17

İki kod parçasını birbirine bağladığınızda, etkili bir şekilde büyük bir kod parçanız olur. Bir kod parçasını korumanın zorluğu en azından boyutunun karesidir ve muhtemelen daha yüksektir.

İnsanlar genellikle tek bir sınıf (/ function / file / etc.) Karmaşıklığına bakar ve gerçekten en küçük ayrılabilir (kapsüllenebilir) birimin karmaşıklığını göz önünde bulundurmanız gerektiğini unuturlar. Dairesel bir bağımlılığa sahip olmak, muhtemelen görünmez bir şekilde, bu birimin boyutunu artırır (siz 1 dosyasını değiştirmeye çalışana ve ayrıca 2-127 dosyalarında da değişiklik yapıldığını fark edene kadar).


14

Kendileri tarafından değil, kötü bir tasarımın göstergesi olarak kötü olabilirler. Eğer Foo, Bar'a, Bar ise Foo'ya bağlıysa, neden benzersiz bir FooBar yerine iki olduklarını sorgulamak doğrulanır.


10

Hmm ... Bu, döngüsel bağımlılık ile ne demek istediğine bağlı, çünkü aslında çok faydalı olduğunu düşündüğüm bazı döngüsel bağımlılıklar var.

Bir XML DOM düşünün - her düğümün ebeveynlerine referans olması ve her ebeveyn için çocuklarının bir listesi olması mantıklıdır. Yapı mantıksal olarak bir ağaçtır, ancak çöp toplama algoritması ya da benzerleri açısından yapı daireseldir.


1
bu bir ağaç olmaz mıydı?
Conrad Frix

@ Conrad: Sanırım bir ağaç olarak düşünülebilir, evet. Neden?
Billy ONeal

1
Ağacın dairesel olduğunu düşünmüyorum çünkü çocuklarını aşağı indirebilir ve sonlandırabilirsiniz (ana referanstan bağımsız olarak). Bir düğüm, aklımda bir ağaç değil bir grafik yapan bir ata olan bir çocuğa sahip değilse.
Conrad Frix

5
Dairesel bir referans, bir düğümün çocuklarından birinin bir ataya geri bağlanması durumunda olacaktır.
Matt Olenik

Bu gerçekten döngüsel bir bağımlılık değildir (en azından herhangi bir soruna yol açacak şekilde değil). Örneğin Node, bunun Nodeiçindeki çocuklar için başka referansları olan bir sınıf olduğunu hayal edin . Sadece kendisine gönderme yaptığı için, sınıf tamamen kendi kendine yetiyor ve başka hiçbir şeye bağlı değil. --- Bu argümanla, özyinelemeli bir işlevin dairesel bir bağımlılık olduğunu iddia edebilirsiniz. Bu ise ancak kötü bir şekilde, (bir streç).
byxor

9

Gibi mi Yumurta mı Tavuk sorun.

Dairesel referansın kaçınılmaz olduğu ve faydalı olduğu birçok durum vardır, ancak aşağıdaki durumda işe yaramadı:

Proje A, B'ye ve B'ye bağlıdır. A'ya bağlıdır. A'da B'den önce derlenmesi gereken B'de kullanılmak üzere derlenmesi gerekir.


6

Buradaki yorumların çoğuna katılıyorum ancak "ebeveyn" / "çocuk" genel referansı için özel bir dava açmak istiyorum.

Bir sınıf genellikle üst veya sahip olduğu sınıf, belki de varsayılan davranış, verilerin geldiği dosyanın adı, sütunu seçen sql ifadesi veya bir günlük dosyasının konumu vb. Hakkında bir şeyler bilmesi gerekir.

Bunu, daha önce "ebeveyn" olan bir kardeş olan bir sınıf içererek döngüsel bir başvuru olmadan yapabilirsiniz, ancak bunu yapmak için mevcut kodu yeniden hesaba katmak her zaman mümkün değildir.

Diğer bir alternatif, bir çocuğun kurucusunda ihtiyaç duyabileceği tüm verileri iletmektir; bu da, son derece sade bir şekilde korkunç olur.


İlgili bir notta, X'in Y'ye atıfta bulunmasının iki genel nedeni olabilir: X, Y'den X'in adına bir şeyler yapmasını isteyebilir veya Y, X'in Y'nin adına Y'yi yapmasını bekliyor olabilir. Eğer Y için var olan tek referanslar, Y adına bir şeyler yapmak isteyen diğer nesnelerin amaçlarına yönelikse, bu tür referansların sahiplerine Y'nin hizmetlerinin artık gerekli olmadığı ve Y'ye atıfta bulunmaları gerektiği söylenmelidir. kolaylıkları.
supercat

5

Veri tabanı açısından, uygun PK / FK ilişkilerine sahip dairesel referanslar veri eklemeyi veya silmeyi imkansız hale getirir. Kayıt tablo b'den silinmediği sürece a tablosundan silemezseniz ve kayıt tablo A'dan çıkmadıkça b tablosundan silemezseniz, silemezsiniz. Ekler ile aynı. bu yüzden birçok veritabanı basamaklı güncellemeler ayarlamanıza izin vermez veya dairesel bir referans varsa silinir, çünkü bir noktada bu mümkün olmaz. Evet, resmen ilan edilmiş PK / Fk dışında bu tür ilişkiler kurabilirsiniz, ancak daha sonra (deneyimimdeki zamanın% 100'ü) veri bütünlüğü sorunları yaşayacaksınız. Bu sadece kötü tasarım.


4

Bu soruyu modelleme bakış açısıyla ele alacağım.

Aslında orada olmayan herhangi bir ilişki eklemediğiniz sürece, güvende olursunuz. Bunları eklerseniz, verilerde daha az bütünlük elde edersiniz (fazlalık olduğu için) ve daha sıkı bir şekilde birleştirilmiş kod elde edersiniz.

Özel olarak dairesel referanslara sahip olan şey, tek bir referans dışında, aslında ihtiyaç duyulacak bir durum görmedim. Ağaçları veya grafikleri modelliyorsanız, buna ihtiyacınız vardır ve tamamen iyidir, çünkü kendi kendine referans, kod kalitesi açısından zararsızdır (bağımlılık eklenmez).

Kişisel olmayan bir referansa ihtiyaç duyduğunuz anda derhal bunu bir grafik olarak modelleyemeyeceğinizi sormalısınız (çoklu varlıkları bir düğümde daraltın). Belki de döngüsel referans yaptığınız bir durum vardır ancak bunu grafik olarak modellemek uygun değildir, ancak bundan şüpheliyim.

İnsanların dairesel bir referansa ihtiyaç duyduklarını düşünmeleri tehlikesi var ama aslında buna ihtiyaç duymuyor. En sık karşılaşılan durum, "Birçoğunun vakası" dır. Örneğin, birincil adres olarak işaretlenmesi gereken birden fazla adrese sahip bir müşteriniz var. Bu durumu iki ayrı ilişki has_address ve is_primary_address_of olarak modellemek çok cazip ama doğru değil. Bunun nedeni ise birincil adres olma kullanıcılar ve adresleri arasındaki ayrı bir ilişki değil ama bunun yerine o bir öznitelik ilişki adresi vardır. Neden? Çünkü etki alanı kullanıcının adresleriyle sınırlı, oradaki tüm adreslerle sınırlı değil. Bağlantılardan birini seçip en güçlü (birincil) olarak işaretlersiniz.

(Şimdi veritabanları hakkında konuşmaya gidiyoruz) Birçok kişi iki ilişki çözümünü tercih ediyor, çünkü "birincil" in benzersiz bir işaretçi olduğunu ve yabancı anahtarın bir tür işaretçi olduğunu anlıyorlar. Yani yabancı anahtar kullanılacak olan şey olmalı, değil mi? Yanlış. Yabancı anahtarlar ilişkileri temsil eder, ancak "birincil" bir ilişki değildir. Bir elemanın her şeyden önce olduğu ve geri kalanının sipariş edilmediği bir düzenin yozlaştırılmış bir halidir. Bir toplam siparişi modellemeniz gerekiyorsa, elbette bunu başka bir seçenek olmadığı için bir ilişkinin niteliği olarak kabul edersiniz. Ancak, onu yozlaştırdığın anda, bir ilişki olarak ilişki olmayan bir şeyi modellemek için bir seçenek ve oldukça korkunç bir durum var. Yani burada geliyor - kesinlikle aşılması gereken bir şey olmayan ilişki fazlalığı.

Bu yüzden, modellediğim şeyden geldiği kesin olmadığı sürece dairesel bir referansın oluşmasına izin vermem.

(not: bu biraz veritabanı tasarımına eğilimlidir, ancak bahse girerim diğer alanlarda da oldukça uygulanabilir)


2

Bu soruya başka bir soru ile cevap verirdim:

Dairesel bir referans modelini tutmaya çalıştığınız şey için en iyi model nerede ?

Tecrübelerime göre, en iyi model, asla demek istediğinizi düşündüğünüz gibi hiçbir zaman dairesel referanslar içermeyecek. Söylendiği gibi, her zaman döngüsel referanslar kullandığınız birçok model var, sadece son derece basit. Ebeveyn -> Çocuk ilişkileri, herhangi bir grafik modeli vb. Ama bunlar iyi bilinen modellerdir ve bence tamamen başka bir şeye atıfta bulunuyorsunuz.


1
Dairesel bağlantılı bir listenin (tek bağlantılı veya çift bağlantılı), "hiç durmaması" gereken bir program için merkezi olay kuyruğu için mükemmel bir veri yapısı olabilir (sıradaki önemli N şeylerini, bayrak kümesini "silmeyin", ardından boş olana kadar sıraya geçin, yeni görevler (geçici veya kalıcı) gerektiğinde, bunları sıra üzerinde uygun bir yere yapıştırın; , daha sonra sıradan alın.
Vatine

1

Veri yapılarındaki dairesel referanslar bazen bir veri modelini ifade etmenin doğal yoludur. Kodlama açısından, kesinlikle ideal değildir ve (bir dereceye kadar) bağımlılık enjeksiyonuyla çözülebilir ve problemi koddan verilere doğru iter.


1

Dairesel referans yapısı yalnızca tasarım açısından değil, aynı zamanda hata yakalama açısından da sorunludur.

Bir kod hatası olasılığını göz önünde bulundurun. Her iki sınıfa da yakalama konusunda doğru bir hata yapmadınız, çünkü yöntemlerinizi henüz o kadar geliştirmediniz veya tembelsiniz. Her iki durumda da, size nelerin geçtiğini söyleyen bir hata mesajınız yok ve hata ayıklamanız gerekiyor. İyi bir program tasarımcısı olarak, hangi yöntemlerin hangi işlemlerle ilgili olduğunu bilirsiniz, böylece hatayı neden olan işlemle ilgili yöntemlerle daraltabilirsiniz.

Dairesel referanslarla sorunlarınız iki katına çıktı. Süreçleriniz sıkı sıkıya bağlı olduğu için, hangi sınıfın hangi sınıfta hataya yol açtığını bilmenin hiçbir yolu yoktur, ya da o zamandan itibaren hata ortaya çıkar, çünkü bir sınıf diğerine bağımlıdır. Şimdi, hangisinden hatadan gerçekten sorumlu olduğunu bulmak için her iki sınıfı birlikte test etmek için zaman harcamanız gerekir.

Tabii ki, uygun hata yakalama bunu çözer, ancak yalnızca bir hatanın ortaya çıkması muhtemel olduğunu biliyorsanız. Genel hata mesajları kullanıyorsanız, hala daha iyi değilsinizdir.


1

Bazı çöp toplayıcıları, onları temizlerken sorun yaşar, çünkü her nesneye başkaları tarafından başvuruda bulunulur.

EDIT: Aşağıdaki yorumlarda belirtildiği gibi, bu sadece bir çöp toplayıcısında son derece saf bir girişim için geçerlidir , pratikte karşılaşacağınız bir şey değil.


11
Hmm .. Bu herhangi bir çöp toplayıcı tarafından tetiklenen gerçek bir çöp toplayıcı değil.
Billy ONeal

11
Dairesel referanslarla ilgili problemleri olan modern bir çöp toplayıcıyı bilmiyorum. Referans sayıları kullanıyorsanız, dairesel referanslar sorunludur, ancak çoğu çöp toplayıcı stil izlemektedir (bilinen referanslar listesine başlar ve diğerlerini bulmak için diğerlerini bulmak için bunları takip eder).
Dean Harding

4
Çeşitli çöp toplayıcıların sakıncalarını açıklayan sct.ethz.ch/teaching/ws2005/semspecver/slides/takano.pdf adresine bakın - uzun duraklama sürelerini azaltmak (örneğin nesiller oluşturmak) , dairesel yapılarla ilgili sorun yaşamaya başlarsınız (dairesel nesneler farklı nesillerdeyken). Eğer referans sayıları alırsanız ve dairesel referans problemini düzeltmeye başlarsanız, uzun duraklama sürelerinin işaret ve tarama karakteristiğinin bir göstergesidir.
Ken Bloom,

Bir çöp toplayıcı Foo'ya bakar ve bu örnekte Bar'a gönderme yapan hafızasını kaldırırsa, Bar'ın kaldırılmasıyla ilgilenmelidir. Bu nedenle, bu noktada çöp toplayıcısının ilerlemesine ve çubuğu çıkarmasına gerek yoktur, çünkü zaten yapılmıştır. Ya da tam tersi, Foo'ya başvuran Bar'ı çıkarırsa, Foo'yu da kaldırır ve böylece Foo'yu çıkarmak zorunda kalmaz, çünkü Bar'ı çıkardığında öyle yapar. Yanılıyorsam beni duzelt lutfen.
Chris

1
Amaç c'de, dairesel referanslar bunu yapar, böylece çöp toplayıcısını yukarı çekerken, ref sayısı sıfıra geldiğinde sıfıra düşmez.
DexterW

-2

Bence sınırsız referanslara sahip olmak program tasarımını kolaylaştırıyor, ancak bazı programlama dillerinin bazı bağlamlarda onlar için destek olmadığını biliyoruz.

Modüller veya sınıflar arasındaki referanslardan bahsettiniz. Bu durumda, programcı tarafından önceden tanımlanmış olan statik bir şeydir ve programcının problemi tam olarak karşılamasa da daireselliği olmayan bir yapı araması yapması açıkça mümkündür.

Asıl problem, bazı problemlerin aslında döngüselliği ortadan kaldıracak şekilde tanımlanamayacağı, çalışma zamanı veri yapılarında döngüselliktedir. Sonuçta - dikte etmesi gereken ve başka bir şey gerektiren bir problem, programcıyı gereksiz bir bulmacayı çözmeye zorluyor.

Bunun araçlarla ilgili bir sorun olduğunu söyleyebilirim, ilke ile ilgili bir sorun değil.


Bir cümle görüşü eklemek, gönderiye önemli bir katkı yapmaz veya cevabı açıklamaz. Bununla ilgili ayrıntılı bilgi verebilir misiniz?

İki nokta, poster aslında modüller veya sınıflar arasındaki referanslardan bahsetti. Bu durumda, programcı tarafından önceden tanımlanmış olan statik bir şeydir ve programcının problemi tam olarak karşılamasa da daireselliği olmayan bir yapı araması yapması açıkça mümkündür. Asıl problem, bazı problemlerin aslında döngüselliği ortadan kaldıracak şekilde tanımlanamayacağı, çalışma zamanı veri yapılarında döngüselliktedir. Sonuçta - dikte etmesi gereken ve başka bir şey gerektiren bir problem, programcıyı gereksiz bir bulmacayı çözmeye zorluyor.
Josh S

Programınızın çalışmaya başlamasının kolaylaştığını buldum, ancak genel olarak konuşmak, sonuçta yazılımın bakımını zorlaştırıyor, çünkü önemsiz değişikliklerin basamaklı etkilere sahip olduğunu fark ettiniz. A, B'ye yapılan aramaları yapar, A'ya yapılan aramaları yapar, bu da B'ye yapılan aramaları yapar ... Bu özellikteki değişikliklerin etkilerini gerçekten anlamak zor, özellikle de A ve B polimorfik olduğunda.
dash-tom-bang
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.