Делай как вGoogle
Скачать 5.77 Mb.
|
433 перешла в другую команду после демонстрации пакета и больше его не использова- ла. Однако никто не менял файл OWNERS . К настоящему моменту тысячи проектов зависят от этого пакета, и мы не можем просто удалить его, не сломав сборку Search и других крупных проектов. Никто не имеет опыта работы с деталями реализации этого пакета. Команда, в которой теперь работает Алиса, возможно, не имеет опыта в устранении тонкостей, обусловленных действием закона Хайрама. Все это говорит о том, что Алисе и другим пользователям пакета предстоит сложное и дорогостоящее обновление под давлением группы безопасности, требующей решить проблему незамедлительно. В этом сценарии обновление охватывает множество вы- пущенных версий пакета за весь период между первоначальным включением пакета в third_party и обнаружением уязвимости. Наши правила в отношении third_party не работают в таких распространенных, к со- жалению, сценариях. Мы понимаем, что нам нужен более высокий уровень владения, чтобы одновременно упростить (и сделать более выгодным) регулярное обновление и усложнить потерю владения для пакетов в third_party . Сложность заключается в том, что очень трудно объяснить разработчикам, что они не должны использовать пакет, который идеально решает их задачу, потому что у нас нет ресурсов для постоянного обновления его версии во всех наших продуктах. Импортирование проектов, пользу- ющихся большой популярностью и не обещающих поддерживать совместимость (как, например, Boost), сопряжено с большим риском: наши разработчики могут быть хорошо знакомы с использованием такой зависимости для решения задач програм- мирования за пределами Google, но не должны позволить ей укорениться в структуре нашей кодовой базы. Ожидаемая продолжительность жизни нашей кодовой базы составляет несколько десятилетий, и внешние проекты, в которых стабильность не является приоритетом, представляют для нее риск. Теория управления зависимостями Теперь, оценив сложность управления зависимостями, обсудим подробнее стоящие перед нами задачи и способы их решения. На протяжении всей этой главы мы снова и снова возвращаемся к формулировке: «Как управлять кодом, получаемым из-за пределов нашей организации (или кодом, который мы не контролируем полностью): как обновлять его, как управлять тем, от чего он зависит?» Мы должны четко пони- мать, что хорошее решение должно исключать любые противоречивые требования к разным версиям, в том числе конфликты версий в ромбовидных зависимостях, даже в динамической экосистеме, в которую могут добавляться новые зависимости или другие требования (в любой точке сети). Мы также должны учитывать влияние времени: любое ПО содержит ошибки, в частности ошибки безопасности, поэтому будет критически важно обновлять некоторые зависимости в течение длительного времени. Стабильная схема управления зависимостями должна быть гибкой в отношении вре- мени и масштаба: мы не можем предполагать бессрочную стабильность узла в графе 434 Глава 21. Управление зависимостями зависимостей и не можем гарантировать, что в код (который мы контролируем или от которого мы зависим) не будут добавляться новые зависимости. Если решение для управления зависимостями предотвращает конфликты между зависимостями, это хорошее решение. Если оно не требует стабильности версий зависимостей или разветвления зависимостей, координации или информирования между организаци- ями и значительных вычислительных ресурсов, то это отличное решение. Можно выделить четыре основных решения: ничего не менять, семантическое версионирование (SemVer), объединение всего необходимого (координация не для каждого проекта, но для каждого дистрибутива) и «жизнь в главной ветви». Ничего не менять (модель статической зависимости) Самый простой способ получить стабильные зависимости — никогда не менять их ни в API, ни в поведении, ни в чем-то еще. Исправления ошибок допускаются, только если от этого не пострадает код, использующий зависимость. Этот подход ориентирован на совместимость и стабильность. Очевидно, что он не идеален из-за предположения бессрочной стабильности. В мире, где не существует проблем без- опасности и исправления ошибок, а зависимости никогда не меняются, этот подход выглядит привлекательно: выполнив все требования версии один раз, мы сможем сохранять ее совместимость с кодом бесконечно долго. Хотя эта модель не является устойчивой в долгосрочной перспективе. Именно ее применяет каждая организация, пока не выяснит, что ожидаемая продолжитель- ность жизни проекта растет. Эта модель хорошо подходит для большинства новых организаций. Сравнительно редко бывает заранее известно, что новый проект просуществует десятилетия и потребуется возможность планомерно обновлять зависимости. Гораздо выгоднее надеяться, что стабильность реальна, и сделать вид, что зависимости будут оставаться стабильными в течение первых нескольких лет развития проекта. Но эта модель оказывается ложной по истечении длительного времени, и момент, ког- да она станет непригодной, наступит внезапно. У нас нет систем заблаговременного предупреждения ошибок безопасности или других критических проблем, которые требуют обновления зависимости, а из-за цепочек зависимостей одно обновление теоретически может заставить организацию обновить всю сеть зависимостей. Выбор версии в этой модели очень прост: организация выбирает любую подходящую версию. SemVer Де-факто современным стандартом управления сетью зависимостей является SemVer (семантическое версионирование) 1 . Это почти повсеместная практика 1 Строго говоря, SemVer не связано с определением требований к совместимости пронуме- рованных версий. Существует множество мелких вариаций этих требований в разных эко- Теория управления зависимостями 435 представления номеров версий для некоторых зависимостей (особенно библиотек) с использованием трех целых чисел, разделенных точками, например 2.4.72 или 1.1.4. Согласно общепринятым соглашениям, эти три числа представляют старший номер версии, младший номер версии и номер исправления. Изменение старшего номера версии указывает на существенное изменение API, которое может нарушать совместимость с предыдущей версией. Изменение младшего номера указывает на расширение функциональных возможностей без ущерба для совместимости. А из- менение номера исправления указывает на изменение деталей реализации (исправ- ление ошибок), не влияющее на API и совместимость. Семантическое разделение номеров версий предполагает, что требование к версии можно выразить как «нечто более новое, чем», за исключением несовместимых из- менений (изменения старшего номера версии). Обычно мы видим требования вида libbase ≥ 1.5 . Такое требование означает совместимость с любой версией libbase с но- мером 1.5 , включая 1.5.1 , и, возможно, с версией 1.6 и выше, но не с libbase 1.4.9 (из-за отсутствия функций, появившихся в версии 1.5 ) или 2.x (некоторые API в libbase могли претерпеть несовместимые изменения). Изменение старшего номера версии сообщает о существенной несовместимости: поскольку существующие возможности изменились (или были удалены), есть вероятность несовместимости для всех за- висимостей. Требования к версии существуют (явно или неявно) всегда, когда одна зависимость использует другую, например « liba требует libbase ≥ 1.5 » и « libb требует libbase ≥ 1.4.7 ». Формализовав эти требования, мы можем определить сеть зависимостей как на- бор программных компонентов (узлов) и требований между ними (ребер). Метки ребер в этой сети изменяются в зависимости от версии исходного узла либо при добавлении (или удалении) зависимостей, либо при обновлении требования из-за изменения исходного узла (которому, например, может потребоваться новая функ- ция, добавленная в зависимость). Поскольку со временем вся эта сеть меняется асинхронно, поиск взаимно совместимого набора зависимостей, удовлетворяющего все транзитивные требования вашего приложения, может оказаться сложной зада- чей 1 . Метод проверки соответствия версий в SemVer действует как решатель задач выполнимости булевых формул (SAT-solvers): он сосредоточен на поиске набора версий для рассматриваемых узлов, удовлетворяющего всем ограничениям, которые задаются требованиями к версиям в ребрах, связывающих зависимости. Большинство экосистем управления пакетами построено на основе таких графов и управляется соответствующими SAT-решателями. SemVer и SAT-решатели не гарантируют существования решения для данного набора ограничений. Ситуации, когда ограничения зависимостей не могут быть удовлетворены, возникают постоянно, как мы уже видели: если компонент нижнего системах, но в целом система «номер версии + ограничения», описанная здесь как SemVer, является репрезентативной практикой. 1 Фактически доказано, что задача применения ограничений SemVer к управлению сетью зависимостей, является NP-полной ( https://research.swtch.com/version-sat ). 436 Глава 21. Управление зависимостями уровня ( libbase ) увеличивает старший номер версии, и при этом обновились неко- торые (но не все) библиотеки, которые зависят от него (например, libb , но не liba ), появляется ромбовидная зависимость. Выбор версии при использовании SemVer сводится к запуску алгоритма поиска версий для зависимостей в сети, которые удовлетворяют всем ограничениям. Если версии невозможно распределить, мы называем эту ситуацию «адом зависимостей». Далее в этой главе мы подробнее рассмотрим некоторые ограничения SemVer. Модели создания комплексных дистрибутивов Мы уже несколько десятилетий наблюдаем применение в нашей отрасли следую- щей мощной модели управления зависимостями: организация формирует набор зависимостей, находит взаимно совместимый набор и выпускает его как коллекцию. Эта модель применяется, например, при создании дистрибутивов Linux — не все компоненты, включенные в дистрибутив, выпускаются одновременно. Фактически многие зависимости нижнего уровня несколько старше зависимостей более высокого уровня из-за разного времени, которое требуется на их интеграцию. Эта модель «формирования и выпуска коллекции» вводит в управление сетью за- висимостей новых участников — дистрибьюторов. Те, кто создает и сопровождает отдельные зависимости, могут ничего или почти ничего не знать о других зависимо- стях, но дистрибьюторы, находящиеся уровнем выше, участвуют в процессе поиска, исправления и тестирования совместимого набора версий, которые необходимо включить в коллекцию. Дистрибьюторами являются инженеры, ответственные за формирование набора версий для объединения, тестирование и решение любых проблем в этом дереве зависимостей. С точки зрения внешнего пользователя это прекрасная модель при условии, что он полагается только на один дистрибутив. По сути, это модель преобразования сети зависимостей в единую комплексную зависимость с присвоением ей номера версии. Вместо «Я полагаюсь на эти 72 библиотеки с этими версиями» можно ска- зать: «Я полагаюсь на RedHat версии N» или «Я полагаюсь на граф NPM в момент времени T». В модели создания комплексных дистрибутивов версии выбирают дистрибьюторы. Жизнь в главной ветви Модель, продвигаемая некоторыми гуглерами 1 , теоретически разумна, но предъяв- ляет ряд новых требований к компонентам сети зависимостей. Она совершенно не похожа на модели, существующие в современной экосистеме открытого ПО, и пока не совсем ясно, как отрасль могла бы перейти к ней. В такой большой организации, как Google, использование этой модели станет дорогим, но эффективным решением, 1 В частности, автор и другие члены сообщества C++ в Google. Теория управления зависимостями 437 и мы чувствуем, что оно более или менее верно распределяет большую часть затрат и стимулов. Мы называем эту модель «жизнь в главной ветви» («Live at head»). Ее можно рассматривать как расширение управления зависимостями в модели раз- работки в главной ветви. Мы расширяем эту модель в той ее части, где говорится о политиках управления версиями применительно к внешним зависимостям. Жизнь в главной ветви предполагает возможность открепить зависимости, отказаться от SemVer и положиться на поставщиков зависимостей, которые будут тестировать изменения во всей экосистеме перед их фиксацией. Жизнь в главной ветви — это явная попытка избежать затрат на решение проблемы управления зависимостями: всегда зависеть только от текущей версии и никогда не вносить никаких изменений, осложняющих адаптацию потребителей. Изменение, которое существенно (и непред- намеренно) меняет API или поведение, будет обнаружено системой непрерывной интеграцией в зависимостях, расположенных ниже в потоке, и, как результат, от- вергнуто при попытке зафиксировать его. В случаях, когда такое изменение дей- ствительно необходимо (например, по соображениям безопасности), оно должно быть выполнено только после обновления нижестоящих зависимостей или создания автоматизированного инструмента для обновления на месте. (Этот инструмент особенно важен для компаний, выпускающих ПО с закрытым исходным кодом: его цель в том, чтобы дать возможность перейти на использование нового API любому пользователю, не обладающему экспертными знаниями об этом API. Это значительно снижает затраты «сторонних наблюдателей» на внесение существенных изменений.) Такой философский сдвиг в ответственности в экосистеме с открытым исходным кодом трудно провести одномоментно: возложение бремени на поставщика API по тестированию и изменению всех его клиентов — это значительное расширение круга обязанностей поставщиков API. Для подтверждения безопасности изменения в модели «жизнь в главной ветви» используются тесты и системы непрерывной интеграции. Если изменение затраги- вает только эффективность или детали реализации, то все тесты будут выполнять- ся успешно, что докажет безопасность изменения и отсутствие разрушительного влияния на пользователей. Изменение, которое меняет синтаксис или семантику экспортируемого API, будет приводить к сотням или даже тысячам ошибок во время тестирования. В этом случае автор изменения должен определить, стоит ли тратить силы и время на устранение этих сбоев ради фиксации изменения. Автор будет взаимодействовать со всеми своими потребителями, чтобы заранее устранить воз- можные ошибки в тестах (то есть избавиться от хрупких предположений в тестах), и, возможно, создаст инструмент для автоматического выполнения необходимого рефакторинга в как можно большем объеме. Структура стимулов и технологические допущения в этой модели необычные: мы предполагаем, что существуют юнит-тесты и система непрерывной интеграции, что поставщики API связаны требованием не нарушать работоспособность подчиненных зависимостей и что потребители API обеспечивают успешное выполнение своих тестов, полагаются на свои зависимости поддерживаемыми способами. Такой подход 438 Глава 21. Управление зависимостями значительно лучше будет работать в экосистеме с открытым исходным кодом (где исправления могут распространяться заранее), чем в условиях скрытых или закры- тых зависимостей. Поставщики API будут стремиться вносить изменения так, чтобы пользователи легко могли мигрировать. Потребители API будут заинтересованы в том, чтобы поддерживать успешное выполнение и полезность тестов. В подходе «жизнь в главной ветви» выбирается последняя стабильная версия за- висимости. Если поставщик будет ответственно подходить к внесению изменений, то зависимость не вызовет сбоев. Ограничения SemVer Подход «жизнь в главной ветви» основан на признанных методах управления версиями (разработка в главной ветви), но его практическая ценность в большом масштабе пока не подтверждена. В настоящее время SemVer является фактическим стандартом управления зависимостями, но этот метод имеет свои ограничения, которые мы обсудим ниже. В определении значений чисел, разделенных точками, на самом деле заложен допол- нительный смысл. Они что-то обещают? Или номер версии носит лишь оценочный характер? Что хотят сообщить разработчики libbase , когда выпускают новую версию и выбирают для нее старший номер, младший номер и номер исправления? Можно ли утверждать, что переход с версии 1.1.4 на версию 1.2.0 безопасен, потому что новая версия отличается от предыдущей только дополнительными возможностями в API и исправлениями ошибок? Конечно, нет. Пользователи могут применить libbase не- обычными, непредусмотренными способами и в результате столкнуться с проблемами при сборке или с изменением поведения после «простого» расширения API 1 . По сути, в отношении совместимости ничего нельзя утверждать, рассматривая только ис- ходный код, — нужно еще знать, о совместимости с чем идет речь. Однако идея «оценки» совместимости начинает ослабевать в контексте сетей зави- симостей и SAT-решателей, применяемых к этим сетям. Существует разница между значениями узлов в традиционных SAT и значениями версий в графе зависимостей при использовании SemVer. Узел в графе SAT имеет значение либо True , либо False Значение версии (1.1.14) в графе зависимостей является оценкой совместимости но- вой версии для кода, использовавшего предыдущую версию. Логику совместимости версий мы строим на шатком фундаменте, рассматривая оценки и собственное мнение как абсолютные величины. Как мы увидим далее, даже если эта логика нормально работает в ограниченных случаях, этого недостаточно для поддержания экосистемы в здоровом состоянии. 1 Например, неудачно реализованное полизаполнение, заранее добавляющее новый API в libbase , может создать противоречие. Или использование API отражения языка в зависи- мости от точного числа API в libbase может приводить к сбою при изменении этого числа. Этого не должно происходить, но если это происходит по ошибке, разработчики libbase не могут доказать совместимость. Ограничения SemVer 439 Признав, что SemVer является неполной оценкой и представляет лишь часть возмож- ного объема изменений, мы можем начать рассматривать его как грубый инструмент. Теоретически его можно использовать для приблизительной оценки. Но на практике, особенно когда мы строим на его основе SAT-решатели, SemVer может подвести нас, излишне ограничивая и недостаточно защищая от ошибок. |