Ад зависимостей

(перенаправлено с «Dependency hell»)

Ад зависимостей (англ. Dependency hell) — антипаттерн управления конфигурацией, разрастание графа взаимных зависимостей программных продуктов и библиотек, приводящее к сложности установки новых и удаления старых продуктов. В сложных случаях различные установленные программные продукты требуют наличия разных версий одной и той же библиотеки. В наиболее сложных случаях один продукт может косвенно потребовать сразу две версии одной и той же библиотеки[1]. Проблемы с зависимостями возникают у общих пакетов/библиотек, у которых некоторые другие пакеты имеют зависимости от несовместимых и различных версий общих пакетов. Если установлена одна версия общего пакета/библиотеки, для решения этой проблемы автоматизатору тестирования/программисту/администратору понадобится получить новые или старые версии зависимых пакетов. Это, в свою очередь, может нарушить работу других зависимых пакетов и добавить проблем в другой набор пакетов, таким образом образуя настоящий ад.

Обзор править

Современные программные системы широко полагаются на существующие пакеты (модули, компоненты, библиотеки) для обеспечения возможности повторного использования и масштабируемости. Менеджеры пакетов на стороне клиента упростили процесс сборки и обеспечили автоматическую поддержку дальнейшей доработки пакетов. В последние годы многие системы перешли к модели, в которой пакеты хранятся в центральном репозитории, доступном через клиентский менеджер пакетов (например, pip для Python, Cargo для Rust, CPAN для Perl и т. д.). В этой модели определение подходящей версии для зависимости от заданного пакета является одной из проблем[2].

Ад зависимостей описывает проблемы и трудности, возникающие при управлении зависимостями в программных проектах, особенно когда речь идет о скриптах для сборки. Сюда входят такие ошибки, как отсутствующие и избыточные зависимости, которые могут привести к сбоям сборки, неправильным результатам и проблемам с производительностью. Существующие методы обнаружения и устранения этих ошибок часто сталкиваются с ограничениями из-за неадекватного представления графов зависимостей[3].

В современной разработке программного обеспечения широко используются системы сборки и соответствующие скрипты сборки для автоматизации преобразования исходного кода в исполняемое программное обеспечение. Системы сборки, такие как Make, GNU Autotools, Ninja, вместе со своими соответствующими скриптами сборки, применяются практически в каждом крупном проекте[3].

Ошибки, связанные с зависимостями, являются наиболее распространенными среди различных типов ошибок сборки (составляют от 52 % до 65 %). Эти ошибки можно разделить на две категории: отсутствующие зависимости и избыточные зависимости. Обе ошибки возникают из-за несоответствия между заявленной зависимостью и фактической зависимостью[3].

Отсутствующая зависимость возникает, когда фактическая зависимость не была объявлена, что приводит к сбоям в параллельной сборке или неправильным результатам в инкрементной сборке. Избыточная зависимость возникает, когда заявленная зависимость не имеет соответствующей фактической зависимости, что приводит к плохой производительности в параллельных или инкрементных сборках[3].

Виды проблем править

Ад зависимостей принимает несколько форм[4]:

Множество зависимостей править

Приложение зависит от большого числа объёмных библиотек, которые требуют длительных скачиваний, занимают много дискового пространства. Возможна ситуация, когда приложение построено на определённой платформе (например Java) и требует установки этой платформы, в то время, как 99 % остальных ваших приложений поддержки этой платформы не требуют. Это частный случай проблемы когда либо приложение использует маленькую часть большой платформы и в конечном итоге требует установки всей платформы (что может быть решено только с помощью рефакторинга приложения), либо маленькое приложение опирается на большое число различных библиотек одновременно.

Длинные цепочки зависимостей править

Приложение зависит от библиотеки «А», которая зависит от библиотеки «Б», … , которая в свою очередь зависит от библиотеки «Я». Это частный случай проблемы множество зависимостей, с той разницей, что большое количество зависимостей осложнено их запутанной и длительной взаимосвязью, которая должна решаться вручную. (Пользователь устанавливает приложение, но ему выдаётся требование установить библиотеку «А», он устанавливает библиотеку «А», но при попытке это сделать библиотека «А» запрашивает установку библиотеки «Б» и т. д.

Иногда наличие такой длинной цепочки зависимостей может приводить к возникновению конфликтов, когда разными компонентами цепочки требуются разные версии одного и того же пакета или библиотеки. (см. конфликтующие зависимости) Такие длинные цепочки зависимостей должны решаться пакетными менеджерами, которые делают это в автоматическом режиме, вместо того чтобы заставлять пользователя решать их ручным способом, при котором зависимости могут остаться частично неудовлетворёнными (не все инсталляторы библиотек постоянно напоминают о всех своих зависимостях пользователю).

Конфликтующие зависимости править

Если «Приложение 1» зависит от библиотеки «А» версии 1.2, а «Приложение 2» зависит от той же библиотеки «А», но уже версии 1.3, и различные версии библиотеки «А» не могут быть одновременно установлены, то «Приложение 1» и «Приложение 2» нельзя одновременно использовать (или даже установить, если установщики проверяют зависимости).

Когда возможно одновременное использование разных версий библиотеки — это решают путём параллельных установок разных версий библиотеки. В противном же случае, установка с использованием новой версии библиотеки должна сопровождаться удалением старой версии библиотеки и, соответственно, всех программ, которые зависят от неё так как они будут неработоспособны.

На Linux-системах эта проблема часто возникает при попытке установки в систему пакетов от разных разработчиков, которые не предназначены для этой версии системы. В таком случае удовлетворение долгой цепи зависимостей пакетов даже может привести, например, к запросу другой версии библиотеки glibc, одной из крайне важных, основополагающих системных библиотек. Если это случается пользователю будет предложено удалить тысячи пакетов, что по сути будет равноценно удалению, например, 80 % системы, включая графические оболочки и сотни различных программ.

Циклические зависимости править

Ситуация, когда приложение «А» версии 2 зависит от приложения «Б», которое зависит от приложения «В», которое в свою очередь зависит от приложения «А», но версии 1. Это приводит к тому, что в пакетных системах типа RPM или DPKG, пользователь должен установить две версии одного и того же исходного приложения «А», что может оказаться недопустимо и не разрешено менеджером пакетов. Однако на Linux-системах наличие циклической зависимости, чаще всего является результатом непонимания пользователем того, как пользоваться операционной системой и её менеджером пакетов.

Решения править

Зависимость от фиксированных версий обеспечивает детерминированность сборок, но может упустить важные исправления в последующих версиях. Зависимость от диапазонов версий позволяет автоматически включать исправления ошибок, обновления безопасности и улучшения производительности, но при этом возникает риск несовместимых изменений. Диапазоны версий позволяют менеджеру пакетов выбрать наилучшую версию на основе указанных ограничений. Конфликты версий могут возникать, когда пакеты имеют множественные (транзитивные) зависимости, что приводит к конфликтам и ошибкам во время выполнения. Некоторые техники, такие как загрузчики классов в OSGi или шаблоны программирования на JavaScript, пытаются смягчить конфликты версий во время выполнения, но многие системы все ещё сталкиваются с проблемами, напоминающими ошибки типа «DLL-hell».

Нумерация версий править

Наиболее очевидное (и часто используемое) решение проблемы — стандартизированная нумерация версий, при которой в программном обеспечении используется специфический номер для каждой версии (aka основная версия), а также дополнительное число для второстепенной версии (aka minor version), например: 10.1, или 5.7. Основная версия изменяется, только, когда программа, которая имеет эту версию, больше не будет совместима с обновленной программой с учётом внесенных в неё изменений. Вспомогательная версия может изменяться даже при небольших изменениях в коде, которые не блокируют стороннему программному обеспечению возможность работы с разрабатываемой программой. В случаях, таких как этот, сторонние программы могут просто запрашивать компонент, имеющий определённую основную версию, и произвольную младшую, второстепенную (больше либо равную определённой минорной версии). Все будет продолжать работать, и зависимости будут разрешены успешно, даже, если второстепенная версия изменилась.

Параллельная установка различных версий ПО править

Решение с нумерацией версий можно улучшить, сделав нумерацию версий поддерживаемой на уровне операционной системы. Это позволит приложению запрашивать модуль/библиотеку по уникальному названию и задавать ограничения на номера версий, эффективно используя операционную систему. Общий модуль может быть помещен в центральное хранилище без риска отказа приложений, которые зависят от предыдущих или последующих версий этого модуля. Каждая версия получает свою собственную запись в хранилище, находясь рядом с другими версиями того же самого модуля. Такое решение используется в операционной системе Windows начиная с Windows Vista, где Global Assembly Cache является имплементацией такого центрального хранилища со связанными сервисами и интегрированным менеджером пакетов.

Хороший пакетный менеджер править

Пакетные менеджеры с программным управлением могут выполнять обновления независимых программных компонентов, одновременно разрешая также несовместимость, связанную с основным номером версии.

Многие современные дистрибутивы Linux имеют пакетные менеджеры, основанные на хранилищах для решения проблемы зависимостей. Эти системы — слой поверх менеджеров пакетов RPM, dpkg, или других пакетных систем, и спроектированы для автоматического разрешения зависимостей путём поиска в предопределенном хранилище программного обеспечения. Обычно эти хранилища программного обеспечения представляют собой FTP или web-сайты, каталоги на локальном компьютере или распределенные через компьютерные сети или, что встречается менее часто, каталоги на съёмных носителях, таких как CD или DVD. Это исключает ад зависимостей для пакетов программного обеспечения, хранящихся в тех репозиториях, которые обычно поддерживаются провайдерами дистрибутивов Linux и на зеркалах по всему миру. Также эти репозитории часто велики, невозможно иметь все части программного обеспечения в них сразу, поэтому ад зависимостей всё же может наступить. В любом случае, специалисты по обслуживанию репозиториев в той или иной мере сталкиваются с адом зависимостей[5]. Примерами таких систем являются APT, YUM, urpmi, Zypper, Portage, Pacman и другие.

PC-BSD (операционная система на базе FreeBSD) до версии 8.2 справляется с адом зависимостей путём размещения пакетов и зависимостей в самодостаточные каталоги-контейнеры, избегая таким образом повреждения системных библиотек при обновлениях или иных их изменениях. Система PC-BSD использует «PBI» (Push Button Installer) как основной пакетный менеджер[6].

Настройки установщика править

Поскольку у различных частей программного обеспечения есть различные зависимости, возможно войти в порочный круг требований к зависимостям, или (что возможно хуже) постоянно расширяющееся дерево требований, поскольку каждый новый пакет требует, чтобы ещё несколько были установлены. Системы, такие как APT, могут разрешать это, предоставляя пользователю ряд решений для выбора и позволяя ему принять или отклонить их по его желанию.

Лёгкая адаптивность в программировании править

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

Программно-аппаратный комплекс править

Другой подход к предотвращению проблем зависимостей состоит в развертывании программного обеспечения через программно-аппаратный комплекс. Зависимости инкапсулируются в модуль, позволяя пользователям не беспокоиться о зависимостях в программном обеспечении. Это забота разработчиков программного обеспечения.

Портативные приложения править

В данном случае подразумеваются приложения (либо версии стандартных приложений), работающие в собственной, замкнутой и самодостаточной, среде и имеющую минимальные зависимости от системных библиотек. Все требуемые для работы программы компоненты добавляются на этапе собственной разработки, кодирования и пакетирования, при этом важные для работы программы файлы и компоненты максимально инкапсулируются в независимой от остальной системы среде, в результате, путём минимального воздействия с остальной системой, удаётся избежать большинства проблем с неразрешёнными зависимостями. Зачастую может работать независимо от системы, на которой приложение запущено. Приложения в RISC OS и ROX Desktop в среде Linux используют application directories, таким образом работая по схожему принципу: программа со всеми своими зависимостями содержится в собственном самодостаточном каталоге (папке)[7].

На других платформах править

На определённых программных платформах, понятие ада зависимостей получает своё собственное название, в зависимости от названия конфликтующих компонентов.

  • DLL hell — процесс, подобный аду зависимостей для систем семейства Microsoft Windows.
  • JAR hell — аналог ада зависимостей в среде Java Runtime Environment.
  • RPM hell — подвид ада зависимостей, присутствующий в Linux-дистрибутивах, использующих RPM-среду и соответствующие пакетные менеджеры (в частности, в дистрибутиве Red Hat)[8].
  • Циклическая перезагрузка обновления 3033929 для MS Windows 7[9]

См. также править

Примечания править

  1. Michael Jang. Linux annoyances for geeks (неопр.). — O’Reilly Media, 2006. — ISBN 9780596552244.
  2. Dietrich et al, 2019.
  3. 1 2 3 4 Fan et al, 2020.
  4. Dependency hell
  5. Pjotr Prins, Jeeva Suresh, and Eelco Dolstra. Nix fixes dependency hell on all Linux distributions. linux.com (22 декабря 2008). — «All popular package managers, including APT, RPM and the FreeBSD Ports Collection, suffer from the problem of destructive upgrades. When you perform an upgrade -- whether for a single application or your entire operating system — the package manager will overwrite the files that are currently on your system with newer versions. As long as packages are always perfectly backward-compatible, this is not a problem, but in the real world, packages are anything but perfectly backward-compatible. Suppose you upgrade Firefox, and your package manager decides that you need a newer version of GTK as well. If the new GTK is not quite backward-compatible, then other applications on your system might suddenly break. In the Windows world a similar problem is known as the DLL hell, but dependency hell is just as much a problem in the Unix world, if not a bigger one, because Unix programs tend to have many external dependencies.». Дата обращения: 22 мая 2013. Архивировано 2 апреля 2017 года.
  6. pbiDIR. Дата обращения: 27 марта 2013. Архивировано 27 марта 2013 года.
  7. Application directories. Дата обращения: 7 сентября 2013. Архивировано 10 ноября 2013 года.
  8. Weinstein, Paul Is Linux Annoying? linuxdevcenter.com (11 сентября 2003). Дата обращения: 10 апреля 2010. Архивировано 7 января 2010 года.
  9. Проблема циклической перезагрузки обновления 3033929. Дата обращения: 15 марта 2015. Архивировано 15 марта 2015 года.

Литература править

Ссылки править