Apa yang salah dengan referensi sirkuler?

160

Saya terlibat dalam diskusi pemrograman hari ini di mana saya membuat beberapa pernyataan yang pada dasarnya mengasumsikan secara aksiomatis bahwa referensi melingkar (antara modul, kelas, apa pun) umumnya buruk. Begitu saya selesai dengan nada saya, rekan kerja saya bertanya, "apa yang salah dengan referensi melingkar?"

Saya memiliki perasaan yang kuat tentang hal ini, tetapi sulit bagi saya untuk mengucapkan secara singkat dan konkret. Penjelasan apa pun yang mungkin saya buat cenderung bergantung pada item lain yang saya anggap juga aksioma ("tidak dapat digunakan dalam isolasi, jadi tidak dapat menguji", "perilaku tidak diketahui / tidak terdefinisi saat keadaan bermutasi pada objek yang berpartisipasi", dll. .), tetapi saya ingin mendengar alasan singkat mengapa referensi melingkar itu buruk yang tidak mengambil lompatan keyakinan seperti yang dimiliki otak saya sendiri, setelah menghabiskan berjam-jam selama bertahun-tahun menguraikannya untuk memahami, memperbaiki, dan memperluas berbagai bit kode.

Sunting: Saya tidak bertanya tentang referensi melingkar yang homogen, seperti yang ada dalam daftar yang tertaut ganda atau pointer-to-parent. Pertanyaan ini benar-benar bertanya tentang referensi melingkar "lingkup yang lebih besar", seperti libA calling libB yang memanggil kembali ke libA. Ganti 'modul' untuk 'lib' jika Anda mau. Terima kasih atas semua jawaban sejauh ini!

dash-tom-bang
sumber
Apakah referensi melingkar berkaitan dengan perpustakaan dan file header? Dalam alur kerja, kode ProjectB baru akan memproses file yang dihasilkan dari kode ProjectA lama. Output dari ProjectA adalah persyaratan baru yang didorong oleh ProjectB; ProjectB memiliki kode yang memfasilitasi penentuan secara umum bidang mana yang dituju, dll. Intinya, ProjectA yang lama dapat menggunakan kembali kode dalam ProjectB baru, dan ProjectB akan bodoh untuk tidak menggunakan kembali kode utilitas dalam ProjectA yang lama (misalnya: deteksi set karakter dan transcoding, merekam parsing, validasi dan transformasi data, dll.).
Luv2code
1
@ Luv2code Hanya menjadi bodoh jika Anda memotong dan menempelkan kode di antara proyek atau mungkin ketika kedua proyek mengkompilasi dan menautkan dalam kode yang sama. Jika mereka berbagi sumber daya seperti ini, masukkan ke perpustakaan.
dash-tom-bang

Jawaban:

220

Ada banyak hal yang salah dengan referensi melingkar:

  • Referensi kelas melingkar membuat kopling tinggi ; kedua kelas harus dikompilasi ulang setiap kali salah satu dari mereka diubah.

  • Edaran perakitan referensi mencegah statis menghubungkan , karena B tergantung pada A, tetapi A tidak dapat dirakit sampai B selesai.

  • Referensi objek melingkar dapat merusak algoritme rekursif naif (seperti serialisator, pengunjung, dan printer cantik) dengan stack overflows. Algoritma yang lebih maju akan memiliki deteksi siklus dan hanya akan gagal dengan pesan pengecualian / kesalahan yang lebih deskriptif.

  • Referensi objek melingkar juga membuat injeksi ketergantungan menjadi tidak mungkin , secara signifikan mengurangi testabilitas sistem Anda.

  • Objek dengan jumlah referensi melingkar yang sangat besar seringkali merupakan Objek Dewa . Bahkan jika tidak, mereka memiliki kecenderungan untuk mengarah pada Kode Spaghetti .

  • Edaran entitas referensi (terutama di database, tetapi juga dalam model domain) mencegah penggunaan kendala non-nullability , yang akhirnya dapat menyebabkan korupsi data atau setidaknya inkonsistensi.

  • Referensi lingkaran pada umumnya hanya membingungkan dan secara drastis meningkatkan beban kognitif ketika mencoba memahami bagaimana suatu program berfungsi.

Tolong, pikirkan anak-anak; hindari referensi melingkar kapan pun Anda bisa.

Aaronaught
sumber
32
Saya terutama menghargai poin terakhir, "beban kognitif" adalah sesuatu yang sangat saya sadari tetapi tidak pernah memiliki istilah ringkas yang hebat untuk itu.
dash-tom-bang
6
Jawaban yang bagus. Akan lebih baik jika Anda mengatakan sesuatu tentang pengujian. Jika modul A dan B saling bergantung, mereka harus diuji bersama. Ini berarti mereka bukan modul yang benar-benar terpisah; bersama-sama mereka adalah satu modul yang rusak.
kevin cline
5
Ketergantungan injeksi bukan tidak mungkin dengan referensi melingkar, bahkan dengan DI otomatis. Seseorang hanya perlu disuntik dengan properti daripada sebagai parameter konstruktor.
BlueRaja - Danny Pflughoeft
3
@ BlueRaja-DannyPflughoeft: Saya menganggap itu sebagai anti-pola, seperti halnya banyak praktisi DI lainnya, karena (a) tidak jelas bahwa properti sebenarnya adalah ketergantungan, dan (b) objek yang "disuntikkan" tidak dapat dengan mudah melacak invariannya sendiri. Lebih buruk lagi, banyak kerangka kerja paling canggih / populer seperti Castle Windsor tidak dapat memberikan pesan kesalahan yang berguna jika ketergantungan tidak dapat diselesaikan; Anda berakhir dengan referensi nol yang menjengkelkan alih-alih penjelasan mendetail tentang ketergantungan mana yang tidak dapat diselesaikan oleh konstruktor. Hanya karena Anda bisa , bukan berarti Anda harus melakukannya .
Aaronaught
3
Saya tidak mengklaim itu adalah praktik yang baik, saya hanya menunjukkan itu tidak mustahil seperti yang diklaim dalam jawabannya.
BlueRaja - Danny Pflughoeft
22

Referensi melingkar dua kali lipat dari referensi non-melingkar.

Jika Foo tahu tentang Bar, dan Bar tahu tentang Foo, Anda memiliki dua hal yang perlu diubah (ketika persyaratan datang, Foos dan Bar tidak boleh lagi saling mengenal). Jika Foo tahu tentang Bar, tetapi Bar tidak tahu tentang Foo, Anda dapat mengubah Foo tanpa menyentuh Bar.

Referensi siklis juga dapat menyebabkan masalah bootstrap, setidaknya di lingkungan yang bertahan lama (layanan dikerahkan, lingkungan pengembangan berbasis gambar), di mana Foo bergantung pada Bar yang bekerja untuk memuat, tetapi Bar juga tergantung pada Foo yang bekerja untuk beban.

Frank Shearar
sumber
17

Saat Anda menyatukan dua bit kode, Anda secara efektif memiliki satu kode besar. Kesulitan mempertahankan sedikit kode setidaknya kuadrat dari ukurannya, dan mungkin lebih tinggi.

Orang sering melihat kompleksitas kelas tunggal (/ fungsi / file / dll.) Dan lupa bahwa Anda benar-benar harus mempertimbangkan kompleksitas unit terkecil yang dapat dipisahkan (dienkapsulasi). Memiliki ketergantungan melingkar meningkatkan ukuran unit itu, mungkin tidak terlihat (sampai Anda mulai mencoba mengubah file 1 dan menyadari bahwa itu juga memerlukan perubahan pada file 2-127).

Alex Feinman
sumber
14

Mereka mungkin buruk bukan dengan sendirinya tetapi sebagai indikator kemungkinan desain yang buruk. Jika Foo bergantung pada Bar dan Bar bergantung pada Foo, maka dibenarkan untuk mempertanyakan mengapa mereka dua dan bukan FooBar yang unik.

mouviciel
sumber
10

Hmm ... itu tergantung apa yang Anda maksud dengan ketergantungan sirkular, karena sebenarnya ada beberapa dependensi sirkular yang menurut saya sangat bermanfaat.

Pertimbangkan XML DOM - masuk akal bagi setiap simpul untuk memiliki referensi ke orang tua mereka, dan bagi setiap orang tua untuk memiliki daftar anak-anaknya. Struktur secara logis adalah pohon, tetapi dari sudut pandang algoritma pengumpulan sampah atau sejenisnya strukturnya berbentuk lingkaran.

Billy ONeal
sumber
1
bukankah itu pohon?
Conrad Frix
@ Conrad: Saya kira itu bisa dianggap sebagai pohon, ya. Mengapa?
Billy ONeal
1
Saya tidak menganggap pohon sebagai lingkaran karena Anda dapat menavigasi anak-anak dan akan berakhir (terlepas dari referensi orang tua). Kecuali sebuah simpul memiliki anak yang juga leluhur yang menurut saya membuatnya menjadi grafik dan bukan pohon.
Conrad Frix
5
Referensi melingkar akan jika salah satu anak dari simpul dilingkarkan kembali ke leluhur.
Matt Olenik
Hal ini tidak benar-benar ketergantungan melingkar (setidaknya tidak dengan cara yang menyebabkan masalah). Misalnya, bayangkan itu Nodeadalah kelas, yang memiliki referensi lain Nodeuntuk anak-anak di dalamnya. Karena itu hanya merujuk sendiri, kelas sepenuhnya mandiri dan tidak digabungkan dengan yang lain. --- Dengan argumen ini, Anda dapat berargumen bahwa fungsi rekursif adalah ketergantungan sirkuler. Ini adalah (di sebuah peregangan), tapi tidak dalam cara yang buruk.
byxor
9

Seperti masalah ayam atau telur .

Ada banyak kasus di mana referensi melingkar tidak dapat dihindari dan berguna tetapi, misalnya, dalam kasus berikut ini tidak berfungsi:

Proyek A tergantung pada proyek B dan B tergantung pada A. A perlu dikompilasi untuk digunakan dalam B yang mengharuskan B dikompilasi sebelum A yang mengharuskan B dikompilasi sebelum A yang ...

Victor Hurdugaci
sumber
6

Sementara saya setuju dengan sebagian besar komentar di sini saya ingin memohon kasus khusus untuk referensi melingkar "orang tua" / "anak".

Kelas sering kali perlu mengetahui sesuatu tentang induknya atau kelas pemiliknya, mungkin perilaku default, nama file asal data, pernyataan sql yang memilih kolom, atau, lokasi file log, dll.

Anda dapat melakukan ini tanpa referensi melingkar dengan memiliki kelas yang berisi sehingga apa yang sebelumnya "orangtua" sekarang adalah saudara kandung, tetapi tidak selalu mungkin untuk faktor ulang kode yang ada untuk melakukan ini.

Alternatif lain adalah dengan memberikan semua data yang mungkin dibutuhkan seorang anak dalam konstruktornya, yang akhirnya menjadi sangat mengerikan.

James Anderson
sumber
Pada catatan terkait, ada dua alasan umum X mungkin memiliki referensi ke Y: X mungkin ingin meminta Y untuk melakukan sesuatu atas nama X, atau Y mungkin mengharapkan X untuk melakukan sesuatu pada Y, atas nama Y. Jika satu-satunya referensi yang ada untuk Y adalah untuk tujuan objek lain yang ingin melakukan hal-hal atas nama Y, maka pemegang referensi tersebut harus diberitahu bahwa layanan Y tidak lagi diperlukan, dan bahwa mereka harus meninggalkan referensi mereka kepada Y di kenyamanan mereka.
supercat
5

Dalam istilah basis data, referensi melingkar dengan hubungan PK / FK yang tepat memungkinkan untuk memasukkan atau menghapus data. Jika Anda tidak dapat menghapus dari tabel a kecuali catatan hilang dari tabel b dan Anda tidak dapat menghapus dari tabel b kecuali catatan hilang dari tabel A, Anda tidak dapat menghapus. Sama dengan sisipan. inilah mengapa banyak basis data tidak memungkinkan Anda untuk mengatur pembaruan berjenjang atau menghapus jika ada referensi melingkar karena pada beberapa titik, itu menjadi tidak mungkin. Ya, Anda dapat mengatur hubungan semacam ini tanpa PK / Fk dinyatakan secara resmi tetapi kemudian Anda akan (100% dari waktu dalam pengalaman saya) memiliki masalah integritas data. Itu hanya desain yang buruk.

HLGEM
sumber
4

Saya akan mengambil pertanyaan ini dari sudut pandang pemodelan.

Selama Anda tidak menambahkan hubungan yang sebenarnya tidak ada, Anda aman. Jika Anda menambahkannya, Anda mendapatkan integritas data yang lebih sedikit (karena ada redundansi) dan kode yang lebih erat.

Masalahnya dengan referensi melingkar secara khusus adalah bahwa saya belum melihat kasus di mana mereka benar-benar diperlukan kecuali referensi satu-diri. Jika Anda membuat model pohon atau grafik, Anda memerlukannya dan itu benar-benar baik-baik saja karena referensi-diri tidak berbahaya dari sudut pandang kualitas kode (tidak ada ketergantungan ditambahkan).

Saya percaya bahwa pada saat Anda mulai memerlukan referensi bukan diri, segera Anda harus bertanya apakah Anda tidak dapat memodelkannya sebagai grafik (pisahkan beberapa entitas menjadi satu - simpul). Mungkin ada kasus di antara di mana Anda membuat referensi melingkar tetapi memodelkannya sebagai grafik tidak sesuai tetapi saya sangat meragukannya.

Ada bahaya bahwa orang berpikir bahwa mereka membutuhkan referensi melingkar tetapi kenyataannya tidak. Kasus yang paling umum adalah "Kasus satu-dari-banyak". Misalnya, Anda telah mendapatkan pelanggan dengan beberapa alamat dari mana satu harus ditandai sebagai alamat utama. Sangat menggoda untuk memodelkan situasi ini karena dua hubungan terpisah has_address dan is_primary_address_of tetapi tidak benar. Alasannya adalah bahwa menjadi alamat utama bukan hubungan terpisah antara pengguna dan alamat, tetapi sebaliknya itu adalah atribut dari hubungan yang memiliki alamat. Mengapa demikian? Karena domainnya terbatas pada alamat pengguna dan tidak untuk semua alamat yang ada. Anda memilih salah satu tautan dan menandainya sebagai yang terkuat (utama).

(Pergi untuk berbicara tentang database sekarang) Banyak orang memilih untuk solusi dua-hubungan karena mereka memahami "primer" sebagai penunjuk unik dan kunci asing adalah semacam penunjuk. Jadi kunci asing harus digunakan, bukan? Salah. Kunci asing mewakili hubungan tetapi "primer" bukan hubungan. Ini adalah kasus degenerasi dari pemesanan di mana satu elemen di atas semua dan sisanya tidak dipesan. Jika Anda perlu memodelkan total pemesanan, Anda tentu akan menganggapnya sebagai atribut hubungan karena pada dasarnya tidak ada pilihan lain. Tetapi pada saat Anda merosotkannya, ada pilihan dan cukup mengerikan - untuk membuat model sesuatu yang bukan hubungan sebagai suatu hubungan. Jadi ini dia - hubungan redundansi yang tentu saja bukan sesuatu yang bisa diremehkan.

Jadi, saya tidak akan membiarkan referensi melingkar terjadi kecuali benar-benar jelas bahwa itu berasal dari hal yang saya modelkan.

(catatan: ini sedikit bias pada desain basis data, tetapi saya berani bertaruh itu juga berlaku untuk area lain)

iklim
sumber
2

Saya akan menjawab pertanyaan itu dengan pertanyaan lain:

Situasi apa yang dapat Anda berikan kepada saya di mana menjaga model referensi melingkar adalah model terbaik untuk apa yang Anda coba bangun?

Dari pengalaman saya, model terbaik tidak akan pernah melibatkan referensi melingkar dalam cara saya pikir Anda bersungguh-sungguh. Yang sedang berkata, ada banyak model di mana Anda menggunakan referensi melingkar sepanjang waktu, itu hanya sangat mendasar. Induk -> Hubungan anak-anak, model grafik apa saja, dll, tetapi ini adalah model-model terkenal dan saya pikir Anda mengacu pada sesuatu yang lain sama sekali.

Yusuf
sumber
1
MUNGKIN bahwa daftar tertaut melingkar (single-linked atau double-linked) akan menjadi struktur data yang sangat baik untuk antrian acara pusat untuk suatu program yang seharusnya "tidak pernah berhenti" (menempelkan hal-hal penting N pada antrian, dengan Kumpulan bendera "jangan dihapus", kemudian cukup lewati antrian sampai kosong; ketika tugas baru (sementara atau permanen) diperlukan, tempel di tempat yang sesuai pada antrian; kapan pun Anda melayani bahkan tanpa tanda "jangan hapus" , lakukan lalu lepaskan dari antrian).
Vatine
1

Referensi melingkar dalam struktur data terkadang merupakan cara alami untuk mengekspresikan model data. Dari sisi pengkodean, ini jelas tidak ideal dan dapat (sampai batas tertentu) diselesaikan dengan injeksi ketergantungan, mendorong masalah dari kode ke data.

Vatine
sumber
1

Konstruk referensi melingkar bermasalah, tidak hanya dari sudut pandang desain, tetapi dari sudut pandang penangkap kesalahan juga.

Pertimbangkan kemungkinan kegagalan kode. Anda belum menempatkan kesalahan yang tepat di kedua kelas, baik karena Anda belum mengembangkan metode Anda sejauh itu, atau Anda malas. Either way, Anda tidak memiliki pesan kesalahan untuk memberi tahu Anda apa yang terjadi, dan Anda perlu men-debug itu. Sebagai perancang program yang baik, Anda tahu metode apa yang terkait dengan proses apa, sehingga Anda dapat mempersempitnya ke metode yang relevan dengan proses yang menyebabkan kesalahan.

Dengan referensi melingkar, masalah Anda sekarang menjadi dua kali lipat. Karena proses Anda terikat erat, Anda tidak memiliki cara untuk mengetahui metode mana di kelas mana yang mungkin menyebabkan kesalahan, atau dari mana kesalahan itu terjadi, karena satu kelas tergantung pada yang lain tergantung pada yang lain. Anda sekarang harus menghabiskan waktu menguji kedua kelas dalam hubungannya untuk mencari tahu mana yang benar-benar bertanggung jawab atas kesalahan tersebut.

Tentu saja, penangkapan kesalahan yang tepat menyelesaikan ini, tetapi hanya jika Anda tahu kapan kesalahan mungkin terjadi. Dan jika Anda menggunakan pesan kesalahan umum, Anda masih tidak jauh lebih baik.

Zibbobz
sumber
1

Beberapa pengumpul sampah kesulitan membersihkannya, karena setiap objek dirujuk oleh yang lain.

EDIT: Seperti dicatat oleh komentar di bawah ini, ini hanya berlaku untuk upaya yang sangat naif pada pengumpul sampah, bukan yang pernah Anda temui dalam praktek.

shmuelp
sumber
11
Hmm .. pengumpul sampah yang tersandung oleh ini bukan pengumpul sampah sejati.
Billy ONeal
11
Saya tidak tahu ada pengumpul sampah modern yang akan memiliki masalah dengan referensi melingkar. Referensi melingkar adalah masalah jika Anda menggunakan jumlah referensi, tetapi sebagian besar pemulung adalah gaya penelusuran (di mana Anda mulai dengan daftar referensi yang diketahui dan mengikuti mereka untuk menemukan yang lainnya, mengumpulkan yang lainnya).
Dean Harding
4
Lihat sct.ethz.ch/teaching/ws2005/semspecver/slides/takano.pdf yang menjelaskan kelemahan berbagai jenis pengumpul sampah - jika mengambil tanda dan menyapu dan mulai mengoptimalkannya untuk mengurangi waktu jeda yang panjang (misalnya membuat generasi) , Anda mulai memiliki masalah dengan struktur lingkaran (ketika objek melingkar berada pada generasi yang berbeda). Jika Anda menghitung jumlah referensi dan mulai memperbaiki masalah referensi melingkar, Anda akhirnya memperkenalkan waktu jeda panjang adalah ciri khas dari tanda dan sapuan.
Ken Bloom
Jika seorang pemulung melihat Foo dan membatalkan ingatannya, yang dalam contoh ini merujuk pada Bar, ia harus menangani penghapusan Bar. Jadi pada titik ini tidak perlu pengumpul sampah untuk terus maju dan menghapus bar karena sudah melakukannya. Atau sebaliknya, jika menghapus Bar yang mereferensikan Foo itu shuold juga menghapus Foo dan dengan demikian ia tidak perlu pergi menghapus Foo karena itu melakukannya ketika dihapus Bar? Tolong koreksi saya jika saya salah.
Chris
1
Dalam objektif-c, referensi melingkar membuatnya sehingga jumlah referensi tidak mencapai nol ketika Anda melepaskan, yang naik ke pengumpul sampah.
DexterW
-2

Menurut pendapat saya memiliki referensi tidak terbatas membuat desain program lebih mudah, tetapi kita semua tahu bahwa beberapa bahasa pemrograman kurang mendukung mereka dalam beberapa konteks.

Anda menyebutkan referensi antar modul atau kelas. Dalam hal ini adalah hal yang statis, yang telah ditentukan oleh pemrogram, dan itu jelas mungkin bagi pemrogram untuk mencari struktur yang tidak memiliki bundar, meskipun mungkin tidak cocok dengan masalah dengan bersih.

Masalah sebenarnya datang dalam sirkularitas dalam struktur data run time, di mana beberapa masalah sebenarnya tidak dapat didefinisikan dengan cara yang menghilangkan sirkularitas. Pada akhirnya - masalah yang harus menentukan dan membutuhkan hal lain adalah memaksa programmer untuk memecahkan teka-teki yang tidak perlu.

Saya akan mengatakan itu masalah dengan alat bukan masalah dengan prinsip.

Josh S
sumber
Menambahkan satu kalimat pendapat tidak berkontribusi secara signifikan ke posting atau menjelaskan jawabannya. Bisakah Anda menguraikan ini?
Nah dua poin, poster itu sebenarnya menyebutkan referensi antar modul atau kelas. Dalam hal ini, itu adalah hal yang statis, yang telah ditentukan oleh pemrogram, dan sangat mungkin bagi pemrogram untuk mencari struktur yang tidak memiliki sirkularitas, meskipun mungkin tidak cocok dengan masalah dengan bersih. Masalah sebenarnya datang dalam sirkularitas dalam struktur data run time, di mana beberapa masalah sebenarnya tidak dapat didefinisikan dengan cara yang menghilangkan sirkularitas. Pada akhirnya - masalah yang harus menentukan dan membutuhkan hal lain adalah memaksa programmer untuk memecahkan teka-teki yang tidak perlu.
Josh S
Saya telah menemukan bahwa hal itu membuatnya lebih mudah untuk menjalankan dan menjalankan program Anda tetapi secara umum itu pada akhirnya membuatnya lebih sulit untuk memelihara perangkat lunak karena Anda menemukan bahwa perubahan sepele memiliki efek cascading. A membuat panggilan ke B yang membuat panggilan kembali ke A yang membuat panggilan kembali ke B ... Saya merasa sulit untuk benar-benar memahami efek dari perubahan sifat ini, terutama ketika A dan B bersifat polimorfik.
dash-tom-bang