Що не так з круговими посиланнями?


160

Я сьогодні брав участь у дискусії з програмування, де я висловив деякі твердження, які в основному аксіоматично припускали, що кругові посилання (між модулями, класами, що завгодно) загалом погані. Після того, як я перебрався зі своїм кроком, мій колега запитав: "що не так з круговими посиланнями?"

У мене є сильні почуття з цього приводу, але мені важко вербалізувати лаконічно і конкретно. Будь-яке пояснення, яке я можу придумати, покладається на інші предмети, які я теж вважаю аксіомами ("не можу використовувати ізольовано, тому не можу перевірити", "невідома / невизначена поведінка як мутація стану в об'єктах, що беруть участь" тощо. .), але я хотів би почути коротку причину того, чому кругові посилання погані, що не сприймають види стрибків віри, які робить мій власний мозок, витративши протягом багатьох годин, розплутуючи їх, щоб зрозуміти, виправити, і розширити різні біти коду.

Редагувати: я не запитую про однорідні кругові посилання, наприклад, у подвійному зв’язку списку чи вказівника на батьків. Це питання справді задає циркулярні посилання "більшого обсягу", як-от libA, що викликає libB, який повертається до libA. Замініть "модуль" на "lib", якщо хочете. Дякую за всі відповіді поки що!


Чи стосується кругової посилання на бібліотеки та файли заголовків? У робочому процесі новий код ProjectB буде обробляти файл, який виводиться зі застарілого коду ProjectA. Цей результат від ProjectA є новою вимогою, зумовленою ProjectB; ProjectB має код, який полегшує загальне визначення того, які поля йдуть куди і т. Д. Зрозуміло, старий ProjectA міг би повторно використовувати код у новому ProjectB, а ProjectB було б нерозумно не використовувати повторно корисний код у застарілому ProjectA (наприклад: виявлення та перекодування набору символів, аналіз записів, перевірка та перетворення даних тощо).
Luv2code

1
@ Luv2code Дурним стає лише якщо вирізати та вставити код між проектами чи, можливо, коли обидва проекти збираються та посилаються в один і той же код. Якщо вони поділяють такі ресурси, покладіть їх у бібліотеку.
Даш-Том-Банг

Відповіді:


220

З круглими посиланнями багато чого не так:

  • Циркулярні посилання класу створюють високу зв’язок ; обидва класи повинні бути перекомпільовані щоразу, коли будь-який з них змінюється.

  • Посилання на кругову збірку запобігають статичному зв’язку , оскільки B залежить від A, але A неможливо зібрати, поки B не завершиться.

  • Кругові посилання на об'єкти можуть зірвати наївні рекурсивні алгоритми (такі як серіалізатори, відвідувачі та симпатичні принтери) із переповненням стека. Більш просунуті алгоритми будуть виявляти цикл і просто відмовлятимуться з більш описовим повідомленням про виняток / помилку.

  • Циркулярні посилання на об'єкти також роблять неможливим введення залежності , значно зменшуючи доказовість вашої системи.

  • Об'єкти з дуже великою кількістю кругових посилань часто є об'єктами Бога . Навіть якщо їх немає, вони мають тенденцію вести до кодексу спагетті .

  • Кругові об'єктні посилання (особливо в базах даних, але і в моделях домену) запобігти використанню без обмежень допустимості порожніх , які в кінцевому підсумку можуть привести до пошкодження даних або по крайней мере непослідовності.

  • Циркулярні посилання взагалі просто заплутують і різко збільшують когнітивне навантаження при спробі зрозуміти, як функціонує програма.

Будь ласка, подумайте про дітей; уникайте кругових посилань, коли зможете.


32
Я особливо ціную останній пункт, "когнітивне навантаження" - це те, чого я дуже усвідомлюю, але ніколи не мав для цього великого стислого терміна.
Даш-Том-Банг

6
Хороша відповідь. Було б краще, якщо ви щось сказали про тестування. Якщо модулі A і B взаємно залежать, їх потрібно перевірити разом. Це означає, що вони насправді не є окремими модулями; разом вони є одним зламаним модулем.
кевін клайн

5
Ін'єкція залежності не є неможливою за допомогою кругових посилань, навіть із автоматичним DI. Потрібно буде просто ввести властивість, а не як параметр конструктора.
BlueRaja - Danny Pflughoeft

3
@ BlueRaja-DannyPflughoeft: Я вважаю, що анти-модель, як і багато інших практикуючих DI, тому що (а) не ясно, що властивість насправді є залежністю, і (b) об'єкт, який "вводиться", не може легко слідкувати за власними інваріантами. Гірше, що багато найскладніших / популярних фреймворків, таких як Castle Windsor, не можуть давати корисні повідомлення про помилки, якщо залежність не вдається вирішити; в кінцевому підсумку ви маєте набридливу нульову посилання замість детального пояснення того, яка саме залежність, в якій конструктор не вдалося вирішити. Тільки тому, що ти можеш , не означає, що ти повинен .
Aaronaught

3
Я не стверджував, що це хороша практика, я просто вказував, що це неможливо, як стверджується у відповіді.
BlueRaja - Danny Pflughoeft

22

Циркулярне посилання є вдвічі більше з'єднання некруглої ланки.

Якщо Foo знає про Bar, а Bar знає про Foo, у вас є дві речі, які потребують змін (коли виникає вимога, що Foos і Bars більше не повинні знати один про одного). Якщо Foo знає про Bar, але Bar не знає про Foo, ви можете змінити Foo, не торкаючись Bar.

Циклічні посилання також можуть спричинити проблеми завантаження, принаймні, у середовищах, що тривають довгий час (розгорнуті сервіси, середовища розробки на основі зображень), де Foo залежить від роботи Bar для завантаження, але Bar також залежить від Foo, що працює для того, щоб навантаження.


17

Зв'язавши два біти коду разом, ви фактично маєте один великий шматок коду. Складність у підтримці трохи коду - принаймні квадрат його розміру, а можливо і більший.

Люди часто дивляться на складність одного класу (/ функція / файл / тощо) і забувають, що ви дійсно повинні враховувати складність найменшої роздільної (інкапсульованої) одиниці. Наявність кругової залежності збільшує розмір цього блоку, можливо, непомітно (поки ви не почнете намагатися змінити файл 1 і не зрозумієте, що також потрібні зміни у файлах 2-127).


14

Вони можуть бути поганими не самі по собі, але як показник можливого поганого дизайну. Якщо Foo залежить від Bar, а Bar залежить від Foo, виправдано сумніватися, чому їх два замість унікального FooBar.


10

Хм ... це залежить від того, що ви маєте на увазі під циркулярною залежністю, бо насправді є деякі кругові залежності, які, на мою думку, є дуже вигідними.

Розглянемо XML DOM - має сенс кожен вузол мати посилання на свого батька, а для кожного з батьків - список своїх дітей. Структура логічно є деревом, але з точки зору алгоритму збору сміття або подібного будова є круговою.


1
хіба це не було б дерево?
Конрад Фрікс

@Conrad: Я думаю, це можна вважати деревом, так. Чому?
Біллі ONeal

1
Я не думаю, що дерево є круглим, тому що ви можете орієнтуватися вниз по його дітям і закінчуватиметься (незалежно від батьківського посилання). Якщо у вузла не було дитини, це також був предком, який, на мій погляд, робить його графіком, а не деревом.
Конрад Фрікс

5
Циркулярним посиланням було б, якби хтось із дітей вузла зациклився на предці.
Метт Оленік

Це не дуже циклічна залежність (по крайней мере , не таким чином , що викликає які - небудь проблеми). Наприклад, уявіть, що Nodeце клас, який має інші посилання на Nodeдітей всередині себе. Оскільки це лише посилання на себе, клас є повністю самостійним і не пов'язаний ні з чим іншим. --- За допомогою цього аргументу можна стверджувати, що рекурсивна функція - це кругова залежність. Це є (на ділянці), але не в поганому сенсі.
byxor

9

Це як курка чи проблема з яйцем .

Є багато випадків, коли кругові посилання неминучі і корисні, але, наприклад, у наступному випадку це не працює:

Проект A залежить від проекту B, а B залежить від A. Необхідно скласти A, щоб використовувати його в B, який вимагає, щоб B був складений перед A, для чого потрібно B бути складеним перед A, який ...


6

Хоча я згоден з більшістю зауважень тут, я хотів би попросити окрему справу для кругової посилання на "батьків" / "дитини".

Класу часто потрібно щось знати про його батьківський чи власний клас, можливо, поведінку за замовчуванням, назву файлу, з якого отримані дані, оператор sql, який вибрав стовпець, або про розташування файлу журналу тощо.

Ви можете зробити це без кругової посилання, маючи клас, що містить, так що те, що раніше було "батьківським", тепер є рідним братом, але не завжди можливо повторно розмістити чинний код для цього.

Інша альтернатива - передавати всі дані, які можуть знадобитися дитині в його конструкторі, які в кінцевому підсумку є просто простими.


У відповідній примітці є дві загальні причини, по яких X може посилатися на Y: X може захотіти попросити Y робити речі від імені X, або Y може очікувати, що X зробить справи Y, від імені Y. Якщо єдині посилання, які існують на Y, призначені для інших об'єктів, які хочуть робити речі від імені Y, то власникам таких посилань слід сказати, що послуги Y більше не потрібні, і що вони повинні відмовитися від своїх посилань на Y в їх зручність.
supercat

5

З точки зору бази даних, кругові посилання з належними PK / FK стосунками унеможливлюють вставлення або видалення даних. Якщо ви не можете видалити з таблиці a, якщо запис не з таблиці b, і ви не можете видалити з таблиці b, якщо запис не з таблиці A, ви не можете видалити. Те саме із вставками. саме тому багато баз даних не дозволяють встановлювати каскадні оновлення або видаляти, якщо є кругова посилання, оскільки в якийсь момент це стає неможливим. Так, ви можете встановити подібні відносини з офіційною заявою PK / Fk, але тоді у вас (100% мого досвіду) виникнуть проблеми з цілісністю даних. Це просто поганий дизайн.


4

Це питання я візьму з точки зору моделювання.

Поки ви не додасте жодних стосунків, яких насправді немає, ви в безпеці. Якщо ви їх додаєте, ви отримуєте меншу цілісність даних (тому що є надмірність) і більш щільно пов'язаний код.

Річ із циркулярними посиланнями полягає в тому, що я не бачив жодного випадку, коли вони були б фактично потрібні, окрім однієї - самонавіювання. Якщо ви моделюєте дерева або графіки, вам це потрібно, і це абсолютно добре, тому що само посилання нешкідливе з точки зору якості коду (не додається залежність).

Я вважаю, що в момент, коли вам починають потрібні несамостійні посилання, негайно вам слід запитати, чи не можете ви її моделювати як графік (згортайте кілька сутностей на один вузол). Можливо, є такий випадок, коли ви робите круговий довідник, але моделювати його як графік не доречно, але я дуже сумніваюся в цьому.

Існує небезпека, що люди думають, що їм потрібна циркулярна довідка, але насправді цього немає. Найпоширеніший випадок - "Справа" один із багатьох ". Наприклад, у вас є клієнт з декількома адресами, з яких одна повинна бути позначена як основна адреса. Дуже спокусливо моделювати цю ситуацію як два окремих відносини has_address та is_primary_address_of, але це не правильно. Причина полягає в тому, що, будучи основною адресою, це не окремий зв’язок між користувачами та адресами, а натомість це атрибут відносини, який має адресу. Чому так? Тому що його домен обмежений адресами користувача, а не всіма адресами. Ви вибираєте одне з посилань і відзначаєте його як найсильніший (первинний).

(Зараз поговоримо про бази даних) Багато людей вибирають рішення для двох відносин, оскільки розуміють "первинний" як унікальний покажчик, а зовнішній ключ є своєрідним вказівником. Тож іноземний ключ повинен бути предметом для використання, правда? Неправильно. Зовнішні ключі представляють відносини, але "первинний" - це не відносини. Це вироджений випадок впорядкування, коли один елемент вище, а решта не впорядкований. Якщо вам потрібно моделювати повне замовлення, ви, звичайно, вважаєте це атрибутом відносин, оскільки іншого вибору в основному немає. Але в той момент, коли ви її вироджуєте, є вибір і досить жахливий - моделювати щось, що не є стосунком як відносинами. Отож ось що - це надмірність відносин, яку, звичайно, не варто недооцінювати.

Отже, я б не допустив циркулярного посилання, якщо не буде абсолютно зрозуміло, що це походить від речі, яку я моделюю.

(зауважте: це дещо упереджено до дизайну баз даних, але я б сподівався, що це досить застосовно і до інших областей)


2

Я відповів би на це питання ще одним питанням:

Яку ситуацію ви можете дати мені, коли збереження кругової еталонної моделі є найкращою моделлю для того, що ви намагаєтесь побудувати?

З мого досвіду, найкраща модель майже ніколи не матиме кругових посилань так, як я думаю, ви це маєте на увазі. При цьому, існує маса моделей, де ви постійно використовуєте циркулярні посилання, це просто надзвичайно просто. Батько -> Дочірні стосунки, будь-яка модель графіка тощо, але це добре відомі моделі, і я думаю, ви маєте на увазі зовсім інше.


1
МОЖЕ бути так, щоб круговий зв'язаний список (односхилий або подвійний зв’язок) був відмінною структурою даних для центральної черги подій для програми, яка повинна "ніколи не зупинятися" (дотримуйтесь важливих N речей на черзі, Набір прапорів "не видаляти", а потім просто переходити по черзі до порожнього; коли потрібні нові завдання (тимчасові чи постійні), вставляйте їх у відповідне місце в черзі; коли ви обслуговуєте парний без прапора "не видаляйте" , зробіть це потім зніміть його з черги).
Ватін

1

Циркулярні посилання в структурах даних іноді є природним способом вираження моделі даних. Що стосується кодування, це, безумовно, не є ідеальним, і його можна (певною мірою) вирішити шляхом ін'єкції залежності, пересуваючи проблему від коду до даних.


1

Кругова опорна конструкція є проблематичною не лише з точки зору проектування, але й з точки зору помилок у сприйнятті.

Розглянемо можливість відмови коду. Ви не розмістили належних помилок у будь-якому класі, або тому, що ще не розробили своїх методів, або ви ледачі. У будь-якому випадку у вас немає повідомлення про помилку, щоб повідомити, що виявилося, і вам потрібно налагодити його. Як хороший дизайнер програми, ви знаєте, які методи пов'язані з якими процесами, тож ви можете звузити його до тих методів, що стосуються процесу, який спричинив помилку.

З круглими посиланнями ваші проблеми тепер подвоїлися. Оскільки ваші процеси щільно пов'язані, ви не можете знати, який метод, у якому класі може бути причиною помилки, або звідки виникла помилка, оскільки один клас залежать від іншого, залежить від іншого. Тепер вам доведеться витратити час на тестування обох класів спільно, щоб з’ясувати, який з них дійсно відповідає за помилку.

Звичайно, правильне введення помилок вирішує це, але лише якщо ви знаєте, коли можлива помилка. І якщо ви використовуєте загальні повідомлення про помилки, вам все-таки не дуже добре.


1

Деякі збирачі сміття мають проблеми з їх очищенням, оскільки на кожен об'єкт посилається інший.

EDIT: Як зазначається в коментарях нижче, це справедливо лише для надзвичайно наївної спроби збору сміття, не тієї, з якою ви коли-небудь стикалися б на практиці.


11
Хм .. будь-який сміттєзбірник, який спрацьовує цим, не є справжнім сміттєзбірником.
Біллі ONeal

11
Я не знаю жодного сучасного сміттєзбірника, який би мав проблеми з циркулярними посиланнями. Циркулярні посилання є проблемою, якщо ви використовуєте довідкові підрахунки, але більшість збирачів сміття відстежують стиль (де ви починаєте зі списку відомих посилань і дотримуєтесь їх, щоб знайти всі інші, збираючи все інше).
Дін Хардінг

4
Див. Sct.ethz.ch/teaching/ws2005/semspecver/slides/takano.pdf, який пояснює недоліки різних типів сміттєзбірників - якщо взяти позначку і почати оптимізувати її, щоб зменшити тривалі паузи (наприклад, створення поколінь) , у вас починають виникати проблеми з круговими структурами (коли кругові предмети є у різних поколінь). Якщо ви взяли підрахунок еталонів і почали виправляти кругову задачу, ви закінчите введення довгих часових пауз, характерних для позначки та розгортки.
Кен Блум

Якщо збирач сміття дивився на Foo і розміщував його пам'ять, яка в цьому прикладі посилається на Bar, він повинен обробляти видалення Bar. Таким чином, на даний момент немає необхідності в тому, щоб збирач сміття йшов уперед та прибирав смугу, оскільки це вже було. Або навпаки, якщо він видалить Bar, які посилання Foo, він також повинен видалити Foo, і, таким чином, йому не потрібно буде видаляти Foo, тому що він це робив, коли він видаляв Bar? Будь ласка, виправте мене, якщо я помиляюся.
Кріс

1
У кругових посиланнях objektiv-c це робиться так, щоб кількість посилань не потрапила до нуля при відпуску, що вимикає сміттєзбірник.
DexterW

-2

На мою думку, наявність необмежених посилань спрощує дизайн програми, але всі ми знаємо, що в деяких мовах програмування їм не вистачає підтримки в деяких контекстах.

Ви згадали посилання між модулями або класами. У такому випадку це статична річ, заздалегідь визначена програмістом, і програмісту, очевидно, можна шукати структуру, якій не вистачає циркулярності, хоча це може не відповідати проблемі.

Справжня проблема полягає в циркулярності в структурах даних часу виконання, де деякі проблеми насправді неможливо визначити таким чином, щоб позбутися циркулярності. Зрештою, проте - це проблема, яка повинна диктувати і вимагати нічого іншого - змушує програміста вирішувати зайву головоломку.

Я б сказав, що проблема з інструментами, а не проблема з принципом.


Додавання думки в одному реченні суттєво не сприяє публікації та поясненню відповіді. Не могли б ви детальніше зупинитися на цьому?

Ну два пункти, плакат фактично згадував посилання між модулями або класами. У такому випадку це статична річ, заздалегідь визначена програмістом, і програмісту, очевидно, можна шукати структуру, якій не вистачає циркулярності, хоча це може не відповідати проблемі. Справжня проблема полягає в циркулярності в структурах даних часу виконання, де деякі проблеми насправді неможливо визначити таким чином, щоб позбутися циркулярності. Зрештою, проте - це проблема, яка повинна диктувати і вимагати нічого іншого - змушує програміста вирішувати зайву головоломку.
Josh S

Я виявив, що це полегшує запуск і запуск вашої програми, але загалом кажучи, це в кінцевому підсумку ускладнює підтримку програмного забезпечення, оскільки ви виявите, що тривіальні зміни мають каскадний ефект. A здійснює дзвінки в B, які здійснюють дзвінки назад до A, що робить дзвінки назад до B ... Я вважав, що важко по-справжньому зрозуміти наслідки змін такого характеру, особливо коли A і B є поліморфними.
Даш-Том-Банг
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.