Семантическое Версионирование 2.0.0
Учитывая номер версии МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ, следует увеличивать:
- МАЖОРНУЮ версию, когда сделаны обратно несовместимые изменения API.
- МИНОРНУЮ версию, когда вы добавляете новую функциональность, не нарушая обратной совместимости.
- ПАТЧ-версию, когда вы делаете обратно совместимые исправления.
Дополнительные обозначения для предрелизных и билд-метаданных возможны как дополнения к МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ формату.
Вступление
В мире управления процессом разработки есть понятие «ад зависимостей» (dependency hell). Чем больше растёт ваша система и чем больше библиотек вы интегрируете в ваш проект, тем больше вероятность оказаться в этой ситуации.
В системе с множественными зависимостями выпуск новой версии может быстро превратиться в кошмар. Если спецификации зависимости слишком жесткие, вы находитесь в опасности блокирования выпуска новой версии (невозможность обновить пакет без необходимости выпуска новой версии каждой зависимой библиотеки). Если спецификация зависимостей слишком свободна, вас неизбежно настигнет версионное несоответствие (необоснованное предположение совместимости с будущими версиями).
В качестве решения данной проблемы я предлагаю простой набор правил и требований, которые определяют, как назначаются и увеличиваются номера версий. Для того чтобы эта система работала, вам необходимо определить публичный API. Он может быть описан в документации или определяться самим кодом. Главное, чтобы этот API был ясным и точным. Однажды определив публичный API, вы сообщаете об изменениях в нём особым увеличением номера версий. Рассмотрим формат версий X.Y.Z (мажорная, минорная, патч). Баг-фиксы, не влияющие на API, увеличивают патч-версию, обратно совместимые добавления/изменения API увеличивают минорную версию и обратно несовместимые изменения API увеличивают мажорную версию.
Я называю эту систему «Семантическое Версионирование» (Semantic Versioning). По этой схеме номера версий и то, как они изменяются, передают смысл содержания исходного кода и что было модифицировано от одной версии к другой.
Спецификация Семантического Версионирования (SemVer)
Слова «ДОЛЖЕН» (MUST), «НЕ ДОЛЖЕН» (MUST NOT), «ОБЯЗАТЕЛЬНО» (REQUIRED), «СЛЕДУЕТ» (SHOULD), «НЕ СЛЕДУЕТ» (SHOULD NOT), «РЕКОМЕНДОВАННЫЙ» (RECOMMENDED), «МОЖЕТ» (MAY) и «НЕОБЯЗАТЕЛЬНЫЙ» (OPTIONAL) в этом документе должны быть интерпретированы в соответствии с RFC 2119.
ПО, использующее Семантическое Версионирование, должно объявить публичный API. Этот API может быть объявлен самим кодом или существовать строго в документации. Как бы ни было это сделано, он должен быть точным и исчерпывающим.
Обычный номер версии ДОЛЖЕН иметь формат X.Y.Z, где X, Y и Z — неотрицательные целые числа и НЕ ДОЛЖНЫ начинаться с нуля. X — мажорная версия, Y — минорная версия и Z — патч-версия. Каждый элемент ДОЛЖЕН увеличиваться численно. Например: 1.9.0 ->1.10.0 -> 1.11.0.
После релиза новой версии пакета содержание этой версии НЕ ДОЛЖНО быть модифицировано. Любые изменения ДОЛЖНЫ быть выпущены как новая версия.
Мажорная версия ноль (0.y.z) предназначена для начальной разработки. Всё может измениться в любой момент. Публичный API не должен рассматриваться как стабильный.
Версия 1.0.0 определяет публичный API. После этого релиза номера версий увеличиваются в зависимости от того, как изменяется публичный API.
Патч-версия Z (x.y.Z | x > 0) ДОЛЖНА быть увеличена только если содержит обратно совместимые баг-фиксы. Определение баг-фикс означает внутренние изменения, которые исправляют некорректное поведение.
Минорная версия (x.Y.z | x > 0) ДОЛЖНА быть увеличена, если в публичном API представлена новая обратно совместимая функциональность. Версия ДОЛЖНА быть увеличена, если какая-либо функциональность публичного API помечена как устаревшая (deprecated). Версия МОЖЕТ быть увеличена в случае реализации новой функциональности или существенного усовершенствования в приватном коде. Версия МОЖЕТ включать в себя изменения, характерные для патчей. Патч-версия ДОЛЖНА быть обнулена, когда увеличивается минорная версия.
Мажорная версия X (X.y.z | X > 0) ДОЛЖНА быть увеличена, если в публичном API представлены какие-либо обратно несовместимые изменения. Она МОЖЕТ включать в себя изменения, характерные для уровня минорных версий и патчей. Когда увеличивается мажорная версия, минорная и патч-версия ДОЛЖНЫ быть обнулены.
Предрелизная версия МОЖЕТ быть обозначена добавлением дефиса и серией разделённых точкой идентификаторов, следующих сразу за патч-версией. Идентификаторы ДОЛЖНЫ содержать только ASCII буквенно-цифровые символы и дефис [0-9A-Za-z-]. Идентификаторы НЕ ДОЛЖНЫ быть пустыми. Числовые идентификаторы НЕ ДОЛЖНЫ начинаться с нуля. Предрелизные версии имеют более низкий приоритет, чем соответствующая релизная версия. Предрелизная версия указывает на то, что эта версия не стабильна и может не удовлетворять требованиям совместимости, обозначенными соответствующей нормальной версией. Примеры: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92.
Сборочные метаданные МОГУТ быть обозначены добавлением знака плюс и ряда разделённых точкой идентификаторов, следующих сразу за патчем или предрелизной версией. Идентификаторы ДОЛЖНЫ содержать только ASCII буквенно-цифровые символы и дефис [0-9A-Za-z-]. Идентификаторы НЕ ДОЛЖНЫ быть пустыми. Сборочные метаданные СЛЕДУЕТ игнорировать, когда определяется старшинство версий. Поэтому два пакета с одинаковой версией, но разными сборочными метаданными, рассматриваются как одна и та же версия. Примеры: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85.
Приоритет определяет, как версии соотносятся друг с другом, когда упорядочиваются. Приоритет версий ДОЛЖЕН рассчитываться путём разделения номеров версий на мажорную, минорную, патч и предрелизные идентификаторы. Именно в такой последовательности (сборочные метаданные не фигурируют в расчёте). Приоритет определяется по первому отличию при сравнении каждого из этих идентификаторов слева направо: Мажорная, минорная и патч-версия всегда сравниваются численно. Пример: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1. Когда мажорная, минорная и патч-версия равны, предрелизная версия имеет более низкий приоритет, чем нормальная версия. Пример: 1.0.0-alpha < 1.0.0. Приоритет двух предрелизных версий с одинаковыми мажорной, минорной и патч-версией ДОЛЖНЫ быть определены сравнением каждого разделённого точкой идентификатора слева направо до тех пор, пока различие не будет найдено следующим образом: идентификаторы, состоящие только из цифр, сравниваются численно; буквенные идентификаторы или дефисы сравниваются лексически в ASCII-порядке. Численные идентификаторы всегда имеют низший приоритет, чем символьные. Больший набор предрелизных символов имеет больший приоритет, чем меньший набор, если сравниваемые идентификаторы равны. Пример: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
Зачем использовать семантическое версионирование?
Это не новая или революционная идея. Вероятно, вы уже используете что-то подобное. Проблема в том, что «подобное» — не достаточно хорошо. Без соответствия формальной спецификации, номера версий практически бесполезны для управления зависимостями. Ясно определив и сформулировав идею версионирования, становится легче сообщать о намерениях пользователям вашего ПО. Когда эти намерения ясны, гибки (но не слишком), спецификации зависимостей наконец могут быть созданы.
Простой пример демонстрирует, как Семантическое Версионирование может сделать «ад зависимостей» вещью из прошлого. Представим библиотеку, названную «Firetruck». Она требует Семантически Версионированный пакет под названием «Ladder». Когда Firetruck был создан, Ladder был 3.1.0 версии. Так как Firetruck использует функциональность версии 3.1.0, вы спокойно можете объявить зависимость от Ladder версии 3.1.0, но менее чем 4.0.0. Теперь, когда доступен Ladder 3.1.1 и 3.2.0 версии, вы можете интегрировать его в вашу систему и знать, что он будет совместим с текущей функциональностью.
Как ответственный разработчик, вы, конечно, хотите быть уверены, что все обновления функционируют как заявлено. В реальном мире полный бардак и ничего нельзя с этим поделать. Что вы можете сделать — это дать Семантическому Версионированию предоставить способ выпуска релизов без выпуска новых версий зависимых пакетов и сохранить вам время и нервы.
Если это звучит соблазнительно, всё что вам нужно — это начать использовать Семантическое Версионирование, объявить, что вы его используете, и следовать правилам. Добавьте ссылку на этот сайт в вашем README, тогда пользователи будут знать правила и извлекать из этого пользу.
Что я должен делать с ревизиями в 0.y.z на начальной стадии разработки?
Самое простое — начать разработку с 0.1.0 и затем увеличивать минорную версию для каждого последующего релиза.
Как я узнаю, когда пора делать релиз 1.0.0?
Если ваше ПО используется на продакшене, оно, вероятно, уже должно быть версии 1.0.0. Если у вас стабильный API, от которого зависят пользователи, версия должна быть 1.0.0. Если вы беспокоитесь за обратную совместимость, вероятно, версия вашего ПО уже 1.0.0.
Не препятствует ли это быстрой разработке и коротким итерациям?
Мажорная версия 0 как раз и означает быструю разработку. Если вы изменяете API каждый день, вы должны быть на версии 0.y.z или на отдельной ветке разработки работать над следующей главной версией.
Даже если малейшие обратно несовместимые изменения в публичном API требуют выпуска новой главной версии, не закончится ли это тем, что очень скоро версия станет 42.0.0?
Это вопрос ответственной разработки и предвидения. Несовместимые изменения не должны быть представлены как незначительные в ПО, имеющем много зависимого кода. Стоимость обновления может быть велика. Практика увеличения главных версий релизов с обратно несовместимыми изменениями означает, что вам придётся думать о последствиях ваших изменений и учитывать соотношение цена/качество.
Документирование всего API — слишком много работы!
Это ваша ответственность, как профессионального разработчика, правильно документировать ПО, предназначенное для широкого использования. Управление сложностью ПО очень важная часть поддержки высокой эффективности проекта. Это тяжело сделать, если никто не знает, как использовать ваше ПО или какой метод можно вызывать безопасно. В долгосрочной перспективе Семантическое Версионирование и настойчивость в качественном документировании публичного API поможет всем и всему работать слаженно.
Что мне делать, если я случайно зарелизил обратно несовместимые изменения как минорную версию?
Как только вы поняли, что нарушили спецификации Семантического Версионирования, исправьте проблему и выпустите новую минорную версию, которая исправляет проблему и восстанавливает обратную совместимость. Даже в таких обстоятельствах неприемлемо модифицировать уже выпущенные релизы. Если это необходимо, укажите в документации о нарушении обратной совместимости, версионирования и проинформируйте ваших пользователей, чтобы они знали о нарушении порядка версий.
Что я должен делать, если я обновляю свои собственные зависимости без изменения публичного API?
Это можно рассматривать как совместимые изменения, так как они не влияют на публичный API. ПО, которое явно зависит от тех же зависимостей что и ваш пакет, должно иметь собственные спецификации зависимостей и автор будет уведомлен о возможных конфликтах. Являются ли данные изменения уровня патча или минорного уровня, зависит от того, обновили ли вы свои зависимости чтобы исправить баг или реализовать новую функциональность. В последнем случае, как правило, добавляется некоторое количество дополнительного кода и как следствие, увеличивается минорная версия.
Что если я нечаянно изменил публичный API в несоответствии с изменением номера версии (т.е. код содержит обратно несовместимые изменения в патч-релизе)?
На ваше усмотрение. Если у вас огромная аудитория, которая будет поставлена перед фактом возвращения прежнего поведения API, то лучше выпустить новый релиз с увеличением главной версии, даже несмотря на то, что фикс содержит исправления уровня патча. Запомните, в Семантическом Версионировании номера версий изменяются строго следуя спецификации. Если эти изменения важны для ваших пользователей, используйте номер версии, чтобы информировать их.
Что делать с устаревшей функциональностью?
Объявление функциональности устаревшей — это обычное дело в ходе разработки и часто необходимо для продвижения вперёд. Когда вы объявляете устаревшим часть публичного API, вы должны сделать две вещи: (1) обновить вашу документацию, чтобы дать пользователям узнать об этом изменении; (2) выпустить новый релиз с увеличением минорной версии. Прежде чем вы полностью удалите устаревшую функциональность в релизе с увеличением главной версии, должен быть как минимум один минорный релиз, содержащий объявление функциональности устаревшей, чтобы пользователи могли плавно перейти на новый API.
Есть ли в SemVer лимиты на длину строки версии?
Нет, но руководствуйтесь здравым смыслом. 255 символов в строке версии, пожалуй, перебор. Кроме того, определенные системы могут предъявлять свои собственные ограничения на размер строки.
Об авторе
Авторство спецификаций Семантического Версионирования принадлежит Тому Престон-Вернеру, основателю Gravatars и соучредителю GitHub.
SemVer (Семантическое версионирование) как правильно выбрать когда увеличивать minor, а когда patch?
Учитывая номер версии МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ, следует увеличивать:
МАЖОРНУЮ версию, когда сделаны обратно несовместимые изменения API.
МИНОРНУЮ версию, когда вы добавляете новый функционал, не нарушая обратной совместимости.
ПАТЧ-версию, когда вы делаете обратно совместимые исправления.
Дополнительные обозначения для предрелизных и билд-метаданных возможны как дополнения к МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ формату.
С мажором всё понятно, тут вопросов нет. С минором в принципе тоже, особенно когда мы добавляем целые разделы (в приложении) или новые точки входа (в api)
А вот что делать когда в приложении (да я знаю что semver разрабатывался для api, там везде только и видишь api api, а мне нравится semver я хочу везде ее использовать), в одной таблице добавились новые столбцы — это патч или минор? (вроде как патч, ведь толком ничего не поменялось, но вроде как новый функционал)
В подробном описании написано что патч это только исправление багов (а если «забыли» добавить этот столбец таблицы, это считается за баг?)
Или вот еще пример:
Изменён код определения mime-типа загружаемого файла, была одна функция, а теперь загружена сторонняя библиотека и проверка делается с помощью нее, по сути в коде поменялась одна строчка. Это уже минор и считается новой фичей или это рефакторинг и считается патчем? Ведь теоретически со старой функцией могли быть в будущем проблемы, а с новой этих проблем не будет
А в подробном описании минора указано
Может просто забить на патчи и пользоваться только мажор.минор и вопросов не возникнет ))
rodnaxel / SemanticVersion.md
Учитывая номер версии МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ, следует увеличивать:
- МАЖОРНУЮ версию, когда сделаны обратно несовместимые изменения API.
- МИНОРНУЮ версию, когда вы добавляете новый функционал, не нарушая обратной совместимости.
- ПАТЧ-версию, когда вы делаете обратно совместимые исправления.
- Дополнительные обозначения для предрелизных и билд-метаданных возможны как дополнения к МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ формату.
Спецификация Семантического Версионирования (SemVer)
ПО, использующее Семантическое Версионирование, должно объявить публичный API. Этот API может быть объявлен самим кодом или существовать строго в документации. Как бы ни было это сделано, он должен быть точным и исчерпывающим.
Обычный номер версии ДОЛЖЕН иметь формат X.Y.Z, где X, Y и Z — неотрицательные целые числа и НЕ ДОЛЖНЫ начинаться с нуля. X — мажорная версия, Y — минорная версия и Z — патч-версия. Каждый элемент ДОЛЖЕН увеличиваться численно. Например: 1.9.0 ->1.10.0 -> 1.11.0.
После релиза новой версии пакета содержание этой версии НЕ ДОЛЖНО быть модифицировано. Любые изменения ДОЛЖНЫ быть выпущены как новая версия.
Мажорная версия ноль (0.y.z) предназначена для начальной разработки. Всё может измениться в любой момент. Публичный API не должен рассматриваться как стабильный.
Версия 1.0.0 определяет публичный API. После этого релиза номера версий увеличиваются в зависимости от того, как изменяется публичный API.
Патч-версия Z (x.y.Z | x > 0) ДОЛЖНА быть увеличена только если содержит обратно совместимые баг-фиксы. Определение баг-фикс означает внутренние изменения, которые исправляют некорректное поведение.
Минорная версия (x.Y.z | x > 0) ДОЛЖНА быть увеличена, если в публичном API представлен новый обратно совместимый функционал. Она ДОЛЖНА быть увеличена, если какой-либо функционал публичного API помечен как устаревший (deprecated). Она МОЖЕТ быть увеличена в случае реализации нового функционала или существенного усовершенствования в приватном коде. Она МОЖЕТ включать в себя изменения, характерные для патчей. Патч-версия ДОЛЖНА быть обнулена, когда увеличивается минорная версия.
Мажорная версия X (X.y.z | X > 0) ДОЛЖНА быть увеличена, если в публичном API представлены какие-либо обратно несовместимые изменения. Она МОЖЕТ включать в себя изменения, характерные для уровня минорных версий и патчей. Когда увеличивается мажорная версия, минорная и патч-версия ДОЛЖНЫ быть обнулены.
Предрелизная версия МОЖЕТ быть обозначена добавлением дефиса и серией разделённых точкой идентификаторов, следующих сразу за патч-версией. Идентификаторы ДОЛЖНЫ содержать только ASCII буквенно-цифровые символы и дефис [0-9A-Za-z-]. Идентификаторы НЕ ДОЛЖНЫ быть пустыми. Числовые идентификаторы НЕ ДОЛЖНЫ начинаться с нуля. Предрелизные версии имеют более низкий приоритет, чем соответствующая релизная версия. Предрелизная версия указывает на то, что эта версия не стабильна и может не удовлетворять требованиям совместимости, обозначенными соответствующей нормальной версией. Примеры: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92.
Сборочные метаданные МОГУТ быть обозначены добавлением знака плюс и ряда разделённых точкой идентификаторов, следующих сразу за патч или предрелизной версией. Идентификаторы ДОЛЖНЫ содержать только ASCII буквенно-цифровые символы и дефис [0-9A-Za-z-]. Идентификаторы НЕ ДОЛЖНЫ быть пустыми. Сборочные метаданные СЛЕДУЕТ игнорировать, когда определяется старшинство версий. Поэтому два пакета с одинаковой версией, но разными сборочными метаданными, рассматриваются как одна и та же версия. Примеры: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85.
Приоритет определяет, как версии соотносятся друг с другом, когда упорядочиваются. Приоритет версий ДОЛЖЕН рассчитываться путём разделения номеров версий на мажорную, минорную, патч и предрелизные идентификаторы. Именно в такой последовательности (сборочные метаданные не фигурируют в расчёте). Приоритет определяется по первому отличию при сравнении каждого из этих идентификаторов слева направо: Мажорная, минорная и патч-версия всегда сравниваются численно. Пример: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1.
Когда мажорная, минорная и патч-версия равны, предрелизная версия имеет более низкий приоритет, чем нормальная версия. Пример: 1.0.0-alpha < 1.0.0. Приоритет двух предрелизных версий с одинаковыми мажорной, минорной и патч-версией ДОЛЖНЫ быть определены сравнением каждого разделённого точкой идентификатора слева направо до тех пор, пока различие не будет найдено следующим образом: идентификаторы, состоящие только из цифр, сравниваются численно; буквенные идентификаторы или дефисы сравниваются лексически в ASCII-порядке. Численные идентификаторы всегда имеют низший приоритет, чем символьные. Больший набор предрелизных символов имеет больший приоритет, чем меньший набор, если сравниваемые идентификаторы равны. Пример: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
Что я должен делать с ревизиями в 0.y.z на начальной стадии разработки?
Самое простое — начать разработку с 0.1.0 и затем увеличивать минорную версию для каждого последующего релиза.
Как я узнаю, когда пора делать релиз 1.0.0?
Если ваше ПО используется на продакшене, оно, вероятно, уже должно быть версии 1.0.0. Если у вас стабильный API, от которого зависят пользователи, версия должна быть 1.0.0. Если вы беспокоитесь за обратную совместимость, вероятно, версия вашего ПО уже 1.0.0.
Не препятствует ли это быстрой разработке и коротким итерациям?
Мажорная версия 0 как раз и означает быструю разработку. Если вы изменяете API каждый день, вы должны быть на версии 0.y.z или на отдельной ветке разработки работать над следующей главной версией.
Даже если малейшие обратно несовместимые изменения в публичном API требуют выпуска новой главной версии, не закончится ли это тем, что очень скоро версия станет 42.0.0?
Это вопрос ответственной разработки и предвидения. Несовместимые изменения не должны быть представлены как незначительные в ПО, имеющем много зависимого кода. Стоимость обновления может быть велика. Практика увеличения главных версий релизов с обратно несовместимыми изменениями означает, что вам придётся думать о последствиях ваших изменений и учитывать соотношение цена/качество.
Документирование всего API — слишком много работы!
Это ваша ответственность, как профессионального разработчика, правильно документировать ПО, предназначенное для широкого использования. Управление сложностью ПО очень важная часть поддержки высокой эффективности проекта. Это тяжело сделать, если никто не знает, как использовать ваше ПО или какой метод можно вызывать безопасно. В долгосрочной перспективе Семантическое Версионирование и настойчивость в качественном документировании публичного API поможет всем и всему работать слаженно.
Что мне делать, если я случайно зарелизил обратно несовместимые изменения как минорную версию?
Как только вы поняли, что нарушили спецификации Семантического Версионирования, исправьте проблему и выпустите новую минорную версию, которая исправляет проблему и восстанавливает обратную совместимость. Даже в таких обстоятельствах неприемлемо модифицировать уже выпущенные релизы. Если это необходимо, укажите в документации о нарушении обратной совместимости, версионирования и проинформируйте ваших пользователей, чтобы они знали о нарушении порядка версий.
Что я должен делать, если я обновляю свои собственные зависимости без изменения публичного API?
Это можно рассматривать как совместимые изменения, так как они не влияют на публичный API. ПО, которое явно зависит от тех же зависимостей что и ваш пакет, должно иметь собственные спецификации зависимостей и автор будет уведомлен о возможных конфликтах. Являются ли данные изменения уровня патча или минорного уровня, зависит от того, обновили ли вы свои зависимости чтобы исправить баг или реализовать новый функционал. В последнем случае, как правило, добавляется некоторое количество дополнительного кода и как следствие, увеличивается минорная версия.
Что если я нечаянно изменил публичный API в несоответствии с изменением номера версии (т.е. код содержит обратно несовместимые изменения в патч-релизе)?
На ваше усмотрение. Если у вас огромная аудитория, которая будет поставлена перед фактом возвращения прежнего поведения API, то лучше выпустить новый релиз с увеличением главной версии, даже несмотря на то, что фикс содержит исправления уровня патча. Запомните, в Семантическом Версионировании номера версий изменяются строго следуя спецификации. Если эти изменения важны для ваших пользователей, используйте номер версии, чтобы информировать их.
Что делать с устаревшим функционалом?
Объявление функционала устаревшим — это обычное дело в ходе разработки и часто необходимо для продвижения вперёд. Когда вы объявляете устаревшим часть публичного API, вы должны сделать две вещи: (1) обновить вашу документацию, чтобы дать пользователям узнать об этом изменении; (2) выпустить новый релиз с увеличением минорной версии. Прежде чем вы полностью удалите устаревший функционал в релизе с увеличением главной версии, должен быть как минимум один минорный релиз, содержащий объявление функционала устаревшим, чтобы пользователи могли плавно перейти на новый API.
Есть ли в SemVer лимиты на длину строки версии?
Нет, но руководствуйтесь здравым смыслом. 255 символов в строке версии, пожалуй, перебор. Кроме того, определенные системы могут предъявлять свои собственные ограничения на размер строки.
В публичное api внесено обратно несовместимое изменение какая версия
Совместимость — это когда что-то одно может работать с чем-то другим без дополнительных настроек, переделок или костылей.
Например, есть дизельное топливо. Оно совместимо с дизельными автомобилями — независимо от их марки, года выпуска, пробега или других параметров. А ещё можно приехать на своей машине на любую заправку с дизелем, а не искать специальную заправку только для определённой марки машин. Вот это всё — совместимость.
В ИТ чаще всего говорят про прямую и обратную совместимость, когда в одну сторону совместимость работает, а в другую — нет.
Когда совместимости нет
Допустим, на предприятии есть программа, которая обслуживает базу данных в нестандартном формате. Проходит время, разработчики увольняются и перестают поддерживать программу. Но программа нужна предприятию, поэтому ей продолжают пользоваться.

Чтобы код всегда оставался актуальным и чистым, используют рефакторинг. Про это у нас есть отдельная статья.
Спустя пять лет программа конкретно устарела: в ней нет поддержки современных удобных протоколов и она не работает на новых операционных системах. Но зато она работает со старой базой.
Предприятие ищет какое-то решение своей проблемы, но выясняется, что ни одна программа не совместима с её базой данных. Это ситуация, когда совместимости нет.
Обычная (или прямая) совместимость
Предприятие обращается за помощью к новым разработчикам: нужно сделать программу, которая умеет работать с нужным форматом баз данных. И, кроме этого, в программе должны быть все современные протоколы и поддержка современных операционок. Разработчики создают этот софт. Это прямая совместимость.
Прямая совместимость — это когда что-то одно может работать с другим. Это относительно простая задача для разработчика, потому что для неё достаточно добавить поддержку нужного формата данных или протоколов.

Обратная совместимость
А теперь представим другую ситуацию: разработчики не ушли с предприятия, а продолжили работать над проектом. В итоге за пять лет они придумали новую версию базы: тоже нестандартную, но более быструю. Но данные за пять лет всё ещё живут в старой базе данных, а переносить их нельзя.
Теперь перед программистами стоит противоречие:
- с одной стороны, надо использовать новый формат, потому что он быстрее работает с данными;
- с другой стороны, надо поддерживать старую версию базы, потому что старые данные тоже нужны в работе.
Это обратная совместимость: новая версия программы поддерживает и новое, и старое.
Пример обратной совместимости — операционная система Windows. В каждой новой версии есть поддержка части программ, которые написаны для старых версий. Даже если вы попробуете запустить аудиоплеер, разработанный для Windows 95, в свежей Windows 10, то, вероятно, у вас это получится.
Также обратная совместимость позволяет нам взять игру для PlayStation 4 и запустить её на новой PlayStation 5 (скорее всего). А вот прямой совместимости у них нет: игра, написанная специально для PS5, вряд ли запустится на PS4.

В чём подвох
Обратная совместимость — головная боль для разработчиков. Ради полноценной поддержки старого формата иногда приходится жертвовать скоростью работы или надёжностью.
Например, в новой версии используется более современный протокол передачи данных. Но чтобы старые версии тоже смогли работать, разработчикам приходится поддерживать одновременно два протокола — новый и старый, каждый проверять на ошибки. Чем больше отличий в версиях — тем сложнее это поддерживать. Иногда для реализации старых технологий тратится намного больше ресурсов, чем для такой же работы — в новых.
StasyakOFF Blog
Номер версии представляется как MAJOR.MINOR.PATCH, где увеличивается:
1. MAJOR версия, когда добавляется несовместимое с предыдущей версией api,
2. MINOR версия, когда ты добавляешь обратно-совместимую функциональность,
3. PATCH версия, когда ты добавляешь обратно-совместимое исправление ошибки.
Допускается добавление дополнительных меток для предварительного релиза и метаданных о сборке, как расширение формата MAJOR.MINOR.PATCH
Введение
В мире управления программным обеспечением есть внушающая ужас вещь, под названием «ад зависимостей» (dependency hell). Чем быстрее растет ваша система и чем больше зависимостей от сторонних библиотек она имеет, тем выше вероятность, что, в один прекрасный момент, вы попадете в эту яму отчаяния.
В системах с большим количество зависимостей выпуск новых версий может быстро превратиться в настоящий кошмар.
Для решения этой проблемы предлагается простой набор правил, определяющий, как должны назначаться номера версий. Эти правила основаны (но не ограничены) на, давно используемых, принципах нумерации проприетарного и свободного программного обеспечения. Для использования семантического версионирования необходимо определить публичный API (в виде документации или набора интерфейсов). Это очень важно, т.к. именно по характеру изменений этого API определяется, какую новую версию получит ваше программное обеспечение.
Правила Семантического Версионирования (СВ)
1. Программное обеспечение использующее СВ должно иметь публичный API (в виде документации или набора интерфейсов).
2. Стандартный номер версии должен иметь вид X.Y.Z — где X,Y и Z целые неотрицательные числа и не содержать ведущих нулей (т.е. не допускаются версии вида, например 1.01.0 — правильно 1.1.0). При внесении изменений в ПО должен изменяться один или несколько компонентов версии X, Y или Z.
3. После выпуска новой версии ПО, его содержимое для этой версии не должно меняться.
4. В начале разработки MAJOR версия равно 0, версия имеет вид 0.Y.Z. Публичное API нестабильно. Все изменения API считаются обратно-совместимым, т.е при любом изменении API увеличивается Y компонент.
5. Версия 1.0.0 присваивается публичному API в стабильном состоянии. Фактически в этот момент происходит первый релиз ПО.
6. Z компонент версии (патч-номер версии) должен быть увеличен, только если внесены обратно-совместимые исправления ошибок. Исправление ошибки — это внутреннее изменение, исправляющее некорректное поведение без изменения публичного API. Также он МОЖЕТ увеличиваться при незначительных функциональных улучшениях кода без изменения публичного API.
7. Y компонент версии (минорный номер версии) должен быть увеличен, если добавлена новая обратно-совместимая функциональность в публичный API. Также Y должен быть увеличен если какие-либо функции публичного API помечены как устаревшие. Компонент Y МОЖЕТ увеличиваться, если внесены значительные функциональные улучшения кода без изменения публичного API, при этом Z компонент версии (патч-номер) должен быть сброшен в 0. Если одновременно с улучшениями производились исправления ошибок, то Z компонент версии можно не менять.
8. X компонент версии должен увеличиваться при внесении обратно-несовместимых изменений в публичный API. Если одновременно с этим были обратно-совместимые изменения API и исправление ошибок не меняющих API, то компоненты Y и Z можно не менять. При изменении X компонента, компоненты Y и Z должны быть сброшены в 0.
9. Предрелизные версии могут быть обозначены путем добавления дефиса и идентификаторов, разделенных точкой, сразу после версии патча. Идентификаторы могут содержать только символы из набора [0-9A-Za-z]. Числовые идентификаторы не должны содержать ведущий ноль. Предрелизная версия указывает на то, что соответствующая версия публичного API еще не стабильна. Примеры нумерации: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92
10. Метаданные сборки могут быть добавлены в конец релизной или предрелизной версии после знака «+». Метаданные сборки — это один или несколько непустых идентификаторов разделенных точкой. Идентификаторы могут содержать только символы из набора [0-9A-Za-z]. При определении старшинства версии метаданные сборки не учитываются, т.е. если 2 версии отличаются только метаданными сборки — эти версии имеют одинаковый приоритет. Пример: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85.
11. Старшинство версий должно рассчитываться путем сравнения X, Y, Z. При определении очередности слева направо сравниваются соответствующие компоненты версий. Например: 1.0.0
Зачем использовать Семантическое Версионирование?
СВ не является новой или революционной идеей и вы, наверняка, использовали что-то похоже. Достаточно ли хорошо это «похожее» работает? Проблема заключается в том, что без формальной спецификации правил, сама идея версионирования, как инструмент управления зависимостями, бесполезна. Определяя четкие правила назначения версий, становится легче доносить до пользователей вашего ПО — как изменилось ПО и что нужно предпринять для эффективного управления зависимостями.
Рассмотрим пример в котором есть приложение «Пожарная машина», которое зависит от библиотеки «Лестница». Библиотека «Лестница» использует семантическое версионирование. Приложение «Пожарная машина» использует библиотеку «Лестница» версии 3.1.0, т.к именно в этой версии были добавлены необходимые приложению функции. Это означает, что мы можем указать для приложения «Пожарная машина» диапазон совместимых версий библиотеки «Лестница» [3.1.0, 4.0.0). Это означает, что если в системе будет библиотека «Лестница» версии 3.1.1 или 3.2.5, то приложение «Пожарная машина» будет работать так же хорошо как и с версией 3.1.0
Однако с версией 4.0.0 и выше приложение может быть уже несовместимо.
Как ответственный разработчик вы хотели бы быть уверены, что в новой версии ПО именно те изменения, которые декларируются версией. Однако реальный мир несовершенен, и вы ничего не можете с этим поделать, поэтому просто будьте бдительны. Что в ваших силах сделать, так это позволить Семантическому Версионированию дать Вам разумный способ выпускать и обновлять ПО без необходимости обновления всех зависимостей, экономя ваши нервы и время.
Если все это выглядит привлекательно — все что вам нужно сделать — это объявить что вы используете Семантическое Версионирование и начать следовать правилам! Делайте ссылку на этот пост или на оригинальный текст в Вашем README, чтобы пользователи Вашего ПО понимали Вас лучше.
FAQ
→ Как менять версии формата 0.Y.Z на стадии разработки?
↑Самое простое — это начать с версии 0.1.0, а затем увеличивать Y при каждом новом выпуске, т.е. использовать 0.Y.0 (если вам не принципиально что вошло в новую версию, улучшения или исправления ошибок).
→ Как определить что пора выпускать версию 1.0.0?
↑Как только от Вашего ПО начинает зависеть стороннее ПО или пользователи — смело ставьте версию 1.0.0
→ Если даже при малейшем изменении обратной совместимости в публичном API надо увеличивать X компонент версии, не получится ли так что он быстро вырастет до больших значений, например, 42.0.0?
↑Это вопрос планирования и предвидения развития Вашего ПО. При изменении публичного API следует помнить, что это влечет изменение зависимого ПО, что может быть очень затратно с экономической точки зрения.
→ Как удалять из публичного API устаревшие части?
↑Устаревание кода является неотъемлемой частью разработки ПО и часто необходимо для успешного развития. Когда вы помечаете часть публичного ПО, как устаревшее, вам необходимо выпустить минимум один минорный релиз, чтобы переход на новый код был плавным, и подробно описать в документации произведенные изменения.
→ Есть ли ограничение на длину строки версии?
↑Данная спецификация не накладывает никаких ограничений. Просто используйте здравый смысл.
Политика обратной совместимости при разработке фреймворка на примере Magento 2. Часть 1

*Поломанный кран в офисе Magento и быстрое решение воплощенное в жизнь одним из инженеров — типичный Backward Compatible фикс.
Почему обратная совместимость важна
Разрабатывая фреймворк, после выпуска нескольких его версий, вы сталкиваетесь с тем, что пользователь (или в случае Magento — заказчик), который его купил/скачал/установил, а также программист, который разрабатывает собственные расширения и модули на основе фреймворка хотят чтобы апгрейд между версиями был максимально легким.
Для заказчика/пользователя процесс должен быть экономически эффективным. Затраты, потраченные на переезд на новую версию продукта, в которые входят: сам апгрейд, автоматизированное тестирование после апгрейда, регрессионное тестирование, исправление багов, которые привнесла новая версия платформы, обновление кастомизаций и сторонних модулей; должны быть минимальными.
Программист, разрабатывающий свое расширение под платформу, хочет чтобы его модуль был как можно дольше forward-compatible, и чтобы он знал заранее в каком релизе код его модуля будет поломан изменениями в системе, чтобы он мог заранее подготовить версию своего модуля совместимую с новым релизом.
В итоге разработчик фреймворка приходит к неожиданной дилемме: чем больше изменений нужно сделать (включая багфиксы), тем больше вероятность того, что будут внесены обратно несовместимые изменения, которые кого-то поломают.
Политика обратной совместимости для кода

Семантическое версионирование (SemVer) дает ответ на дилемму, помогая не отказываться ни от одной из опций (частые backward compatible релизы).
- MAJOR — говорит о несовместимых изменениях в API
- MINOR — говорит о том, что была добавлена обратно совместимая функциональность
- PATCH — говорит об обратно совместимом фиксе бага
Концепт публичного и приватного кода
Разработчики имеющие опыт работы с C++ или Java хорошо знакомы с этим концептом. Когда на выходе программа поставляется в виде .h (header) файла, содержащим описания внешних контрактов, семантику методов которых легко прочитать любым текстовым редактором и DLL файла, содержащий собранный и скомпилированный код, который тяжело прочитать и нельзя изменить.
PHP на уровне языка не предоставляет такую возможность. Поэтому «правильный» подход фреймворки, написанные на этом языке, искали давно. Например, Magento 1, как и многие другие фреймворки того времени (Symfony 1) использовали Inheritance Based API, когда фреймворк для кастомизации и расширения своих компонентов, предлагал отнаследоваться от любого из своих классов, и переопределить или расширить поведение в классе наследнике. Соответственно в Magento 1 приватные свойства и методы не использовались вообще, а Magento core разработчики обязаны были следить за двумя контрактами (Public — контракт который формируют публичные свойства, методы и константы сущностей; Protected — контракт наследования сущностей) и предотвращать добавление обратно несовместимых изменений в оба. Когда все сущности и все методы этих сущностей в код базе являются API, то добавление новых изменений и фикс багов в любом случае может кого-то поломать.
В Magento 2 в связи с этим решили следовать другому подходу — Inheritance Based API запретили для внутреннего использования, и не рекомендуют использование такого подхода для кастомизации классов фреймворка или модулей сторонними разработчиками. Запретили использование Protected модификатор доступа для атрибутов и методов классов. Основная идея в этих изменениях заключается в том, что имея только Public и Private — нам нужно следить только за одним контрактом — публичным. У нас нет контракта-наследования.
Следующим шагом было разделение кода на Публичный (аналог header файлов) — код и конструкции отмеченные аннотацией @api и Приватный (аналог скомпилированного DLL) — код, не отмеченный аннотацией, говорящей, что это API.
Закрытый код не предполагается к использованию сторонними разработчиками. Таким образом его изменения приведут только к увеличению PATCH версии компонента, где этот код изменялся.
Изменения в Публичном коде всегда увеличивают MINOR или MAJOR версию компонента.
Мы обещаем быть обратно совместимыми для классов отмеченных @api внутри MINOR и PATCH релизов компонентов. В случае когда нам нужно внести изменения в класс/метод отмеченный как @api , мы отмечаем его как @deprecated и он будет удален не раньше следующего MAJOR релиза компонента.
Примеры того, что попадает под определение Публичного кода в Magento 2
- PHP интерфейсы отмеченные @api
- PHP классы отмеченные @api
- JavaScript интерфейсы отмеченные @api
- JavaScript классы отмеченные @api
- Virtual Type отмеченные @api
- URL paths
- Консольные команды и их аргументы
- Less Variables & Mixins
- Топики очереди сообщений AMQP и их типы данных
- Декларация UI компонентов
- Layout декларация модулей
- События, которые тригерятся модулями
- Схема конфигурации, добавляемая модулями
- Структура системной конфигурации
API vs SPI (Точки Расширения)
PHP контракты в Magento могут быть использованы по-разному. Таких способов использования 3:
- API использование: методы интерфейса вызываются в PHP коде
- Service Provider Interface (SPI) использование: интерфейс может быть реализован, позволяя новой реализации расширять текущее поведение платформы
- API и SPI одновременно
API и SPI не являются взаимоисключающими, поэтому мы не разделяем их отдельно в коде. SPI имеют такую же аннотацию как и API — @api .
Но кто же тогда определяет, что есть API, а что есть SPI? — Те, кто их использует, т.е. внешние разработчики
Правила указания зависимостей
- get
- save
- delete
Так у нас появился отдельный сервис для поиска категорий.
В случае с удалением метода из интерфейса — обратная история, для SPI использование это не ломающие изменения, для API — это проблема. 
Если модуль использует (вызывает) контракты, задекларированные другим модулем Magento, он должен зависеть на MAJOR версию этого модуля. И система предоставляет обратную совместимость в рамках всего мажорного релиза.
SPI (Точки расширения)
Если модуль предлагает свою реализацию для контрактов задекларированных другим Magento модулем он должен зависеть на MAJOR+MINOR версию этого модуля. И система предоставляет обратную совместимость в рамках минорного релиза.
Зависимость на приватный код
Если же модуль зависит на сущность не отмеченную как @api , тогда модуль должен зависеть на MAJOR+MINOR+PATCH версию. И уже апгрейд на следующий патч релиз может обернуться проблемами для данного модуля.
Ближайший мажорный коммерческий релиз 2.2 — Чего ждать?
В текущих версиях Magento 2.0.x и 2.1.x нельзя обойтись без зависимостей на приватный код.
Потому что у нас недостаточное покрытие @api для этого. Некоторые модули не имеют сервис контрактов вообще (например, wishlist). Поэтому у сторонних разработчиков нет другого выхода кроме как зависимости на непомеченные api аннотацией классы.
Разработка API — самый сложный процесс в проектировании и разработке программного обеспечения. И так как мы пока не знаем когда именно закончится работа по добавлению сервис контрактов во все модули Magento мы решили в релизе 2.2 отметить @api все сущности, которые необходимы для написания/кастомизации модулей на мадженто сторонними программистами.
Т.е. в 2.2 мы отметим все «честные контракты», т.е. если функциональность, которую предоставляет модуль реализуется хелпером или ресурс моделью. И получить данную функциональность путем вызова API модуля нельзя, то мы пометим данный хелпер как @api .
Для примера, у нас есть ProductInterface, который определяет набор операций над сущностью продукта. И есть продуктовая модель, которая имплементирует этот интерфейс. Так как сейчас продуктовая модель наследуется от абстрактной модели и соответственно имеет контракт абстрактной модели, то мы не можем сказать сейчас, что сторонний разработчик можем полагаться только на ProductInterface, и если кто-то предоставит свою реализацию этого интерфейса, то сможет легко подменить внутреннюю реализацию мадженто (если это не будет наследник продуктовой модели). Т.е. достаточно много кода в Magento использует метод get/setData который пришел из абстрактной модели. Поэтому в 2.2 мы пометим продуктовую модель как @api , не смотря на то, что у нас уже есть API в виде ProductInterface.
До релиза 2.2. мы считаем весь код — публичным, т.е. на все классы распространяется политика обратной совместимости. Не только на классы, помеченные @api . Разделение концепта на публичный и приватный начнется с 2.2 релиза.
Теперь как мы определяем, что используют, а что нет.
Мы анализируем какие зависимости между модулями Magento использует внутри себя. А также мы анализируем какие зависимости используются экстеншенами на Marketplace (non api dependency). И если зависимости валидны, т.е. такой результат нельзя получить используя текущие API модуля — мы помечаем сущность как @api
*Данная статья является частью 1; Часть 2, которая выйдет вскоре опишет ограничения в коде, которые привносятся политикой обратной совместимости и что мы делаем, чтобы не останавливать рефакторинг следуюя BC политике и не аккумулировать технический долг из-за этих ограничений