{"id":13583,"url":"\/distributions\/13583\/click?bit=1&hash=e33bc0d3a37a74826169363c867d3f9f74deaa73040cb6145c82841335993467","title":"\u041d\u0435\u0439\u0440\u043e\u0441\u0435\u0442\u044c \u042f\u043d\u0434\u0435\u043a\u0441\u0430 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442 \u0432\u0438\u0434\u0435\u043e \u0432 \u043f\u0440\u044f\u043c\u043e\u043c \u044d\u0444\u0438\u0440\u0435","buttonText":"\u041a\u0430\u043a?","imageUuid":"135b72ce-4b43-5240-a9ca-242ab0616d40","isPaidAndBannersEnabled":false}
Auriga

Управление сторонними зависимостями в коде

Я уже больше 10 лет работаю в Web-разработке, поэтому видел довольно много проектов, которые в какой-то момент своего развития получили ворох проблем из-за того, что неграмотно управляли своими зависимостями. Были проекты, которые страдали от того, что сторонние компоненты, которые они использовали, прекращали поддерживаться своими авторами. Были проекты, где лицензия не позволяла использовать библиотеку в коммерческих целях. Были проекты, где зависимости хранились вместе с исходным кодом, и в них были изменения, которые потом не позволяли их обновить.

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

Писать самому или использовать чужой код?

Когда встаёт задача писать новый, доселе невиданный, функционал, то выбора нет – придётся писать. А вот если речь идёт о более-менее стандартных вещах, всегда есть два пути: написать самому с нуля, или использовать чужой, уже готовый код. У обоих подходов есть и плюсы, и минусы, поэтому стоит остановиться, подумать и принять взвешенное решение.

Писать самому:

Плюсы:

  • можно сделать точно под свои требования;
  • легко интегрировать с другими компонентами;
  • можно оставить исходный код закрытым;
  • будет поддерживаться сколь угодно долго;
  • можно управлять качеством кода.

Минусы:

  • долго;
  • дорого;
  • нужно разбираться в нюансах;
  • нужно тестировать;
  • нужно писать документацию;
  • нужно исправлять баги.

Использовать готовый код:

Плюсы:

  • быстро;
  • не обязательно знать все нюансы.

Минусы:

  • как правило, требует доработок;
  • слабые возможности для кастомизации;
  • может не быть поддержки или она может неожиданно прекратиться;
  • качество кода варьируется от идеального до поделия на коленке;
  • сложно вносить изменения.

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

Как выбирать зависимости

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

Лицензии

В первую очередь следует посмотреть на лицензию стороннего компонента. Это довольно простой и понятный шаг, поэтому стоит начать с него.

Открытое ПО очень привлекает своей бесплатностью, но, с точки зрения лицензирования, есть важные нюансы.

Для коммерческой разработки важно, чтобы используемое открытое ПО не «заражало» производный код своей открытостью, то есть не требовало делать открытыми и свои собственные наработки тоже.

Этим требованиям удовлетворяют так называемые «Разрешительные» (permissive) лицензии: BSD, MIT, Apache.

Иногда автор кода предпочитает не выбирать лицензию, а сразу отдаёт своё произведение в общественное достояние (Public Domain). Таким кодом может бесплатно пользоваться кто угодно и как угодно. Эти «лицензии» обозначаются следующим образом: CC0, Unlicense, WTFPL.

Если вы не против открыть свой исходный код, то можно также использовать «Копилефтные» лицензии: GPL, AGPL.

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

Нестандартные лицензии

Иногда открытое ПО распространяется по специально созданной для него лицензии. Реже открытое ПО распространяется сразу под несколькими разными лицензиями, например: бесплатная для некоммерческого использования и платная – для коммерческого. ПО с закрытым исходным кодом чаще всего имеет свою собственную лицензию.

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

Сообщество и мэйнтейнеры

От того, кто является автором ПО, напрямую зависит его качество и надёжность, поэтому стоит внимательно рассмотреть кто и как участвует в проекте.

Авторы

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

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

Мэйнтейнеры

Часто автор заодно является мэйнтейнером. В задачи мэйнтейнера входит регулярный выпуск новых версий. При выборе софта следует обратить внимание на то, как быстро выходят исправления, насколько регулярно появляются новые версии, как долго висят открытыми пулл-реквесты.

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

Сообщество

От размера сообщества зависит, как быстро вам ответят на Stack Overflow, как быстро найдут новые баги. Отчасти, крупное сообщество, защищает от того, что проект будет заброшен.

Цифры

Последнее, на что стоит обратить внимание, – это цифры, например, количество незакрытых багов, количество незакрытых и неоткомментированных пулл-реквестов, количество «звёздочек» на GitHub, покрытие тестами и полнота документации.

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

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

Где хранить зависимости

Плохой вендоринг

В стародавние времена было принято скачивать ПО из интернета (или приносить на дискете) и складывать в отдельную директорию рядом с вашим собственным кодом. Это называется вендоринг.

Раньше это был единственный способ управлять зависимостями, но теперь так делать не стоит по нескольким причинам:

  • Скопированный код трудно обновить, потому что непонятно что от чего зависит и каких версий требует, нет никакой автоматизации и инструментов для сборки, публикации.
  • Есть очень большой соблазн что-то быстренько исправить, подогнать в скопированном коде. Такой код или никогда больше нельзя будет обновить, или при обновлении он всё сломает.

По большому счёту, вендоринг – это copy-paste-переросток со всеми его проблемами.

Управление зависимостями

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

Некоторые утилиты обладают дополнительными возможностями, такими как:

  • разные наборы зависимостей для разных нужд (для тестов одни, для сборки документации другие);
  • уведомления о выходе новых версий;
  • уведомления о найденных уязвимостях;
  • запуск скриптов;
  • проверка хэш-сумм устанавливаемых компонентов.

Более того, многие системы управления зависимостями позволяют совершенно одинаково и прозрачно для разработчика устанавливать их на разных платформах, будь то Windows, Linux или даже Mac на Apple M1.

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

Вот несколько примеров таких инструментов:

  • npm и yarn для JavaScript;
  • pip и Poetry для Python;
  • Maven и Gradle для Java.

Разумеется, файл со списком зависимостей должен распространяться вместе с кодом, чтобы любые разработчики или CI-система могли установить всё, что нужно.

Семантическое версионирование

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

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

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

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

Фиксирование версий

Автоматическое обновление зависимостей — это удобно, но бывают случаи, когда оно неприемлемо. Например, нельзя тестировать продукт с одними версиями зависимостей, а потом собирать релизную сборку с другими. Поэтому для CI-систем версии следует фиксировать строго.

Питоновский Poetry умеет это делать из коробки, JS-овский NPM и Yarn — тоже. Они генерируют второй файл, который содержит всё дерево зависимостей целиком, с их полными версиями и даже хэшами. Если такого механизма в системе управления зависимостями нет, то его несложно эмулировать вручную просто экспортировав в отдельный файл всё дерево установленных зависимостей. С одной стороны, это позволяет получить точно воспроизводимые сборки, когда это нужно, а с другой — автоматически обновлять зависимости, если это не несёт ломающих изменений.

Недостатком такого подхода являются повышенные требования к дисциплине. Разработчики должны понимать, когда можно позволить зависимостям автоматически обновиться, а когда — нет. Нужно знать соответствующие команды системы управления зависимостями. И конечно, файл с деревом зависимостей тоже надо коммитить в репозиторий вместе с кодом и ни в коем случае не пытаться решать в нём мёрдж-конфликты. Как и любой другой автоматически сгенерированный файл, при обнаружении в нём конфликтов нужно просто перегенерировать снова.

Как вносить изменения в чужой код

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

Pull request

Идеальным вариантом будет создание бага или фиче-реквеста в оригинальный репозиторий. Возможно, авторы сами будут не против добавить новую функциональность. Однако чаще бывает, что у них на это нет времени.

Поэтому вторым вариантом будет создание пулл-реквеста в оригинальный репозиторий. Если мейнтейнеры посчитают его годным, изменения могут довольно быстро попасть в релиз.

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

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

[Как бы] хороший вендоринг

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

Форк

Очень много известных проектов получили форки, потому что в какой-то момент оригинальные авторы не смогли удовлетворить чьи-то потребности.

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

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

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

Если форк публичный, то для доставки новых сборок удобно использовать существующие публичные реестры, такие как NPM, PyPI, Maven. Для них не требуется никаких дополнительных настроек инфраструктуры.

Если форк приватный (и лицензия оригинального проекта это позволяет), то можно использовать или self-hosted решения: такие как Nexus, или приватные, но платные версии стандартных реестров. В этом случае придётся немного повозиться с настройками и авторизацией.

Минимизация рисков

Использование стороннего кода — это всегда риски. Риски, которые приходится брать на себя, получая взамен скорость разработки.

Чтобы свести их к минимуму, имеет смысл писать код в модульном режиме, а зависимости оборачивать в обёртки. Таким образом, любую зависимость или модуль в случае проблем можно будет относительно легко заменить на свой или чужой аналог.

Также следует минимизировать количество зависимостей. Например, если есть возможность, то простые в имплементации и тестировании функции можно написать самостоятельно.

Ну и конечно, нужно регулярно обновлять зависимости и прогонять их через системы поиска уязвимостей.

Другие виды зависимостей

Зависимости бывают не только в виде компонентов, поставляемых в исходных кодах. База данных, RESTful API, DLL-библиотека — всё это тоже зависимости и ими тоже надо управлять, учитывать риски и пристально рассматривать лицензии. Но это уже совсем-совсем другая история.

Ссылка на оригинал статьи:

0
Комментарии
Читать все 0 комментариев
null