Сегодня я участвовал в дискуссии по программированию, где сделал несколько заявлений, в которых аксиоматически предполагалось, что циклические ссылки (между модулями, классами и т. Д.), Как правило, плохие. Как только я закончил свою работу, мой коллега спросил: «Что не так с круговыми ссылками?»
У меня есть сильные чувства по этому поводу, но мне трудно выразить словами кратко и конкретно. Любое объяснение, которое я могу придумать, имеет тенденцию полагаться на другие элементы, которые я тоже рассматриваю аксиомами («не могу использовать изолированно, поэтому не могу проверить», «неизвестное / неопределенное поведение, поскольку состояние изменяется в участвующих объектах» и т. Д.). ..., но я бы хотел услышать краткую причину того, почему циркулярные ссылки плохи, потому что они не совершают прыжков веры, которые делает мой собственный мозг, потратив много часов в течение многих лет, распутывая их, чтобы понять, исправить, и расширить различные биты кода.
Редактировать: я не спрашиваю об однородных циклических ссылках, таких как те, что в двусвязном списке или указатель на родителя. Этот вопрос действительно задает циклические ссылки «большего объема», например, когда libA вызывает libB, а затем обращается к libA. Замените «модуль» на «lib», если хотите. Спасибо за все ответы до сих пор!
источник
Ответы:
С циклическими ссылками не так много вещей:
Круговые ссылки на классы создают высокую связь ; оба класса должны быть перекомпилированы каждый раз, когда любой из них изменяется.
Ссылки круговой сборки предотвращают статическое связывание , потому что B зависит от A, но A не может быть собран, пока B не будет завершен.
Круговые ссылки на объекты могут приводить к сбою наивных рекурсивных алгоритмов (таких как сериализаторы, посетители и симпатичные принтеры) с переполнением стека. Более продвинутые алгоритмы будут иметь обнаружение цикла и просто потерпят неудачу с более описательным сообщением об исключении / ошибке.
Круговые ссылки на объекты также делают невозможным внедрение зависимостей , что значительно снижает тестируемость вашей системы.
Объекты с очень большим количеством циклических ссылок часто являются объектами Бога . Даже если они не, они имеют тенденцию вести к Кодексу Спагетти .
Круговые ссылки на сущности (особенно в базах данных, но также и в моделях предметной области) предотвращают использование ограничений , не допускающих обнуление , что в конечном итоге может привести к повреждению данных или, по крайней мере, к несогласованности.
Циркулярные ссылки в целом просто сбивают с толку и резко увеличивают когнитивную нагрузку, пытаясь понять, как функционирует программа.
Пожалуйста, подумайте о детях; избегайте циклических ссылок, когда можете.
источник
Круговая ссылка - это двойная связь некруговой ссылки.
Если Foo знает о Bar, а Bar знает о Foo, у вас есть две вещи, которые нужно изменить (когда возникает требование, что Foos и Bars больше не должны знать друг о друге). Если Foo знает о Bar, но Bar не знает о Foo, вы можете изменить Foo, не касаясь Bar.
Циклические ссылки также могут вызывать проблемы с начальной загрузкой, по крайней мере, в средах, которые длятся долго (развернутые сервисы, среды разработки на основе изображений), где Foo зависит от работы Bar для загрузки, но Bar также зависит от работы Foo для нагрузки.
источник
Когда вы связываете два бита кода вместе, вы фактически получаете один большой кусок кода. Трудность поддержки небольшого количества кода - по крайней мере квадрат его размера, и возможно выше.
Люди часто смотрят на сложность одного класса (/ function / file / etc.) И забывают, что вы действительно должны учитывать сложность наименьшего отделяемого (инкапсулируемого) модуля. Наличие циклической зависимости увеличивает размер этой единицы, возможно, незаметно (пока вы не начнете пытаться изменить файл 1 и не поймете, что это также требует изменений в файлах 2-127).
источник
Они могут быть плохими не сами по себе, а как показатель возможного плохого дизайна. Если Foo зависит от Bar, а Bar зависит от Foo, то уместно задать вопрос, почему их два, а не уникальный FooBar.
источник
Хм ... это зависит от того, что вы подразумеваете под круговой зависимостью, потому что на самом деле есть некоторые круговые зависимости, которые, я думаю, очень полезны.
Рассмотрим XML DOM - для каждого узла имеет смысл иметь ссылку на своего родителя, а для каждого родителя - список его потомков. Структура логически представляет собой дерево, но с точки зрения алгоритма сборки мусора или подобного, структура круговая.
источник
Node
это класс,Node
внутри которого есть другие ссылки на детей. Поскольку он ссылается только на себя, класс полностью автономен и не связан ни с чем другим. --- С этим аргументом вы можете утверждать, что рекурсивная функция является циклической зависимостью. Это является (на участке), но не в плохом смысле.Это как проблема с курицей или яйцом .
Во многих случаях циклическая ссылка неизбежна и полезна, но, например, в следующем случае она не работает:
Проект A зависит от проекта B, а B зависит от A. Необходимо компилировать A для использования в B, который требует компиляции B перед A, который требует компиляции B перед A, который ...
источник
Хотя я согласен с большинством комментариев здесь, я хотел бы сослаться на особый случай для круговой ссылки «родитель» / «ребенок».
Классу часто нужно что-то знать о своем родительском или принадлежащем ему классе, возможно, поведении по умолчанию, имени файла, из которого получены данные, операторе sql, который выбрал столбец, или расположении файла журнала и т. Д.
Вы можете сделать это без циклической ссылки, имея содержащий класс, так что то, что раньше было «родителем», теперь является родным братом, но не всегда возможно перефакторировать существующий код, чтобы сделать это.
Другой альтернативой является передача всех данных, которые могут понадобиться дочернему элементу, в его конструкторе, что в итоге просто ужасно.
источник
В терминах базы данных циклические ссылки с правильными отношениями PK / FK делают невозможным вставку или удаление данных. Если вы не можете удалить из таблицы a, если запись не удалена из таблицы b, и вы не можете удалить из таблицы b, если запись не удалена из таблицы A, вы не можете удалить ее. То же самое со вставками. Вот почему многие базы данных не позволяют вам устанавливать каскадные обновления или удаления, если есть циклическая ссылка, потому что в какой-то момент это становится невозможным. Да, вы можете установить такие отношения без официального объявления PK / Fk, но тогда у вас (в моем опыте 100% случаев) будут проблемы с целостностью данных. Это просто плохой дизайн.
источник
Я возьму этот вопрос с точки зрения моделирования.
Пока вы не добавите никаких отношений, которых на самом деле нет, вы в безопасности. Если вы добавите их, вы получите меньше целостности данных (поскольку существует избыточность) и более тесно связанный код.
Особенность циклических ссылок заключается в том, что я не видел ни одного случая, когда они были бы действительно необходимы, кроме одной ссылки на себя. Если вы моделируете деревья или графики, вам это нужно, и с этим все в порядке, потому что самоссылка безвредна с точки зрения качества кода (без добавления зависимости).
Я полагаю, что в тот момент, когда вы начинаете нуждаться в не-самостоятельной ссылке, сразу же вы должны спросить, не можете ли вы смоделировать ее как граф (сверните несколько сущностей в один узел). Может быть, есть случай между тем, когда вы делаете круговую ссылку, но моделирование ее в виде графика не подходит, но я в этом сильно сомневаюсь.
Существует опасность, что люди думают, что им нужна круговая ссылка, но на самом деле это не так. Наиболее распространенный случай - это случай «один из многих». Например, у вас есть клиент с несколькими адресами, из которых один должен быть помечен как основной. Очень заманчиво смоделировать эту ситуацию как два отдельных отношения has_address и is_primary_address_of, но это не правильно. Причина в том, что, будучи основным адресом, это не отдельная связь между пользователями и адресами, а вместо этого это атрибут отношения имеет адрес, Почему это? Поскольку его домен ограничен адресами пользователя, а не всеми адресами, которые там есть. Вы выбираете одну из ссылок и отмечаете ее как самую сильную (основную).
(Теперь поговорим о базах данных) Многие люди выбирают решение для двух отношений, потому что они понимают «первичный» как уникальный указатель, а внешний ключ является своего рода указателем. То есть внешний ключ должен быть тем, что нужно использовать, верно? Неправильно. Внешние ключи представляют отношения, но «первичные» не являются отношениями. Это вырожденный случай упорядочения, когда один элемент превыше всего, а остальные не упорядочены. Если вам нужно смоделировать общий порядок, вы, конечно, будете рассматривать его как атрибут отношения, потому что другого выбора нет. Но в тот момент, когда вы его дегенерируете, есть выбор, и довольно ужасный - моделировать что-то, что не является отношениями, как отношения. Итак, вот оно, избыточность отношений, которую нельзя недооценивать.
Так что я бы не допустил циклическую ссылку, если не будет абсолютно ясно, что она исходит от того, что я моделирую.
(примечание: это слегка смещает дизайн базы данных, но я бы поспорил, что это вполне применимо и к другим областям)
источник
Я бы ответил на этот вопрос другим вопросом:
Какую ситуацию вы можете мне представить, когда сохранение круговой эталонной модели является лучшей моделью для того, что вы пытаетесь построить?
Исходя из моего опыта, лучшая модель почти никогда не будет включать циклические ссылки в том смысле, в каком, я думаю, вы это имеете в виду. При этом существует множество моделей, в которых вы постоянно используете циклические ссылки, это просто чрезвычайно просто. Родительские -> дочерние отношения, любая графовая модель и т. Д., Но это хорошо известные модели, и я думаю, что вы имеете в виду нечто совсем другое.
источник
Циркулярные ссылки в структурах данных иногда являются естественным способом выражения модели данных. С точки зрения кодирования, это определенно не идеально и может быть (в некоторой степени) решено путем внедрения зависимостей, перенося проблему из кода в данные.
источник
Круговая ссылочная конструкция проблематична не только с точки зрения проектирования, но и с точки зрения обнаружения ошибок.
Рассмотрим возможность сбоя кода. Вы не поместили правильный перехват ошибок ни в один из классов, либо потому, что вы еще не разработали свои методы, либо вы ленивы. В любом случае, у вас нет сообщения об ошибке, чтобы сообщить вам, что произошло, и вам нужно отладить его. Как хороший разработчик программ, вы знаете, какие методы связаны с какими процессами, поэтому вы можете сузить его до тех методов, которые относятся к процессу, вызвавшему ошибку.
С круговыми ссылками ваши проблемы теперь удвоились. Поскольку ваши процессы тесно связаны, у вас нет возможности узнать, какой метод, в каком классе могла быть вызвана ошибка, или откуда возникла ошибка, потому что один класс зависит от другого, зависит от другого. Теперь вам нужно потратить время на тестирование обоих классов одновременно, чтобы выяснить, какой из них действительно ответственен за ошибку.
Конечно, правильная перехват ошибок разрешает это, но только если вы знаете, когда ошибка может произойти. И если вы используете общие сообщения об ошибках, вам все равно не намного лучше.
источник
У некоторых сборщиков мусора возникают проблемы с их очисткой, потому что на каждый объект ссылается другой.
РЕДАКТИРОВАТЬ: Как отмечено в комментариях ниже, это верно только для чрезвычайно наивной попытки сборщика мусора, а не той, с которой вы когда-либо сталкивались на практике.
источник
По моему мнению, наличие неограниченных ссылок облегчает разработку программ, но мы все знаем, что некоторые языки программирования не поддерживают их в некоторых контекстах.
Вы упомянули ссылки между модулями или классами. В этом случае это статическая вещь, предопределенная программистом, и для программиста вполне возможно найти структуру, в которой отсутствует округлость, хотя она может не полностью решить проблему.
Настоящая проблема заключается в цикличности в структурах данных времени выполнения, где некоторые проблемы не могут быть определены таким образом, чтобы избавиться от цикличности. В конце концов - проблема, которая должна продиктовать, а требование чего-то еще заставляет программиста решить ненужную головоломку.
Я бы сказал, что проблема в инструментах, а не в принципе.
источник