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

3 сентября 2026 · ~15 мин чтения

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

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

Семантическое версионирование — это соглашение записывать номер версии тремя числами MAJOR.MINOR.PATCH и менять каждое из них по строгому правилу, чтобы номер сам по себе отвечал на вопрос «сломается ли у меня что-то, если я обновлюсь». Не описание размера работы, а обещание про совместимость.

История

До конца 2000-х номер версии значил всё что угодно. Компании нумеровали релизы под маркетинг: Windows шёл 95 → 98 → 2000 → XP → Vista → 7. Adobe выпускала Photoshop CS3, CS4, CS5. Solaris прыгнул с 2.6 на 7. Никакого общего смысла в цифрах не было — они продавали, а не сообщали.

Пока софт ставили с дисков, это никого особо не мучило. Проблема выросла вместе с менеджерами пакетов. Когда твой проект тянет 40 чужих библиотек, а каждая из них тянет ещё по 20, и всё это обновляется само — тебе физически нужен способ отличить «безопасное обновление» от «после него всё встанет». Разработчики в 2000-х называли это состояние dependency hell (ад зависимостей).

В 2010 году Том Престон-Вернер (Tom Preston-Werner), один из сооснователей GitHub, опубликовал документ Semantic Versioning. Он не изобрёл схему major.minor.patch — так нумеровали десятилетиями. Он сделал другое: записал её как формальную спецификацию с обязательствами. Не «принято считать, что», а «ЕСЛИ ты объявил, что следуешь SemVer, ТО ты обязан».

Вехи:

Владельца в корпоративном смысле у спецификации нет: она опубликована под лицензией Creative Commons, живёт на GitHub, есть официальный перевод на русский на самом semver.org. Читается за 15 минут — это редкий стандарт, который реально можно прочесть целиком.

Что это такое

Версия — это три числа через точки:

2.1.153
│ │  └── PATCH — исправили баг, ничего не сломали
│ └───── MINOR — добавили возможность, старое работает как раньше
└─────── MAJOR — сломали обратную совместимость

Правила простые до неприличия:

Дополнительные правила, которые чаще всего забывают:

Ноль в мажоре — зона без правил. Версии 0.y.z означают «раннюю разработку». Автор ничего не обещает, сломать может в любой момент. Именно поэтому 0.x в проде — всегда осознанный риск.

Опубликованная версия неизменна. Выпустил 1.2.3 — всё, содержимое зафиксировано навсегда. Нашёл ошибку — выпускай 1.2.4. Это не бюрократия, а основа доверия: если 1.2.3 вчера и 1.2.3 сегодня могут отличаться, весь смысл номеров испаряется.

Числа сравниваются как числа, а не как текст. 1.0.10 больше, чем 1.0.9. Ведущие нули запрещены.

Пре-релизы пишутся через дефис и считаются младше обычной версии: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0-rc.1 < 1.0.0. Это позволяет выкатывать кандидаты, не занимая финальный номер.

Метаданные сборки пишутся через плюс — 1.0.0+20260903.a1b2c3 — и при сравнении игнорируются полностью. Это место для хеша коммита или номера сборки, а не для смысла.

SemVer vs CalVer. Календарное версионирование ставит в номер дату: Ubuntu 24.04 — это апрель 2024 года, JetBrains 2026.1 — первый релиз года. CalVer отвечает на вопрос «насколько это старое», SemVer — на вопрос «сломается ли». Разные вопросы, поэтому это не конкуренты, а выбор в зависимости от того, что важнее твоим пользователям.

SemVer vs маркетинговое версионирование. Chrome и Firefox инкрементируют мажор каждые четыре недели просто по календарю релизов. Ядро Linux перешло с 2.6.39 на 3.0 в 2011 году, и Линус Торвальдс прямо сказал, что причина — юбилей и то, что цифры стали слишком длинными, а не поломка совместимости. Это не SemVer и не притворяется им.

SemVer — это обещание, а не свойство

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

Аналогии из жизни

Издания книги

Третье издание, исправленное и дополненное. PATCH — допечатка тиража с исправленными опечатками, страницы те же. MINOR — дополненное издание: добавили главу в конец, всё прежнее на местах. MAJOR — новое издание с перекроенной структурой: главы переставлены, нумерация страниц уехала, и все ссылки «см. с. 245» в чужих работах теперь ведут не туда.

Где ломается: у книги нет автоматической проверки. Если ты цитируешь второе издание, а читатель держит третье — никто не выдаст ошибку, человек просто откроет не ту страницу и не поймёт почему. В софте наоборот: несовместимость чаще всего проявляется громко и сразу. И ещё: издательство нигде формально не обещает, что в «дополненном» издании старая нумерация уцелеет. У SemVer это обещание — центральное.

Розетки и вилки

PATCH — внутри розетки заменили пружинящий контакт на более надёжный, снаружи ничего не изменилось. MINOR — рядом с двумя гнёздами добавили USB-порт: новая возможность, старые вилки втыкаются как прежде. MAJOR — сменился тип розетки, европейская вилка в британскую не лезет физически.

Где ломается: в электрике совместимость двоичная — влезло или не влезло. В софте она частичная и коварная: код может успешно подключиться к новой версии библиотеки, отработать без единой ошибки и выдать другой результат. Хуже того — важнейший параметр розетки, напряжение, в форме вилки вообще не отражён: 110 В и 220 В бывают на одинаковых разъёмах. В SemVer ровно та же дыра — номер версии молчит про то, что функция стала работать в десять раз медленнее или начала жрать втрое больше памяти. Формально совместимо, практически катастрофа.

Меню ресторана

PATCH — повар перешёл с крупной соли на мелкую, вкус тот же. MINOR — в меню добавили три новых позиции, старые никуда не делись. MAJOR — карбонару теперь готовят со сливками, а том-ям убрали совсем.

Где ломается: ресторан не хранит прошлые версии меню — нельзя «откатиться на прошлый квартал», а в софте старые версии остаются доступны годами, и это принципиально. И вторая трещина: посетитель с аллергией зависел от того, чего в меню написано не было. Повар честно считал замену масла мелочью уровня PATCH. Для этого гостя это было ломающее изменение.

Как это работает

Разберём механику по шагам — от автора библиотеки до твоего компьютера.

Шаг 1. Автор решает, что изменилось. Он смотрит на публичный API (публичный интерфейс — то, чем пользуются снаружи) и отвечает: может ли существующий чужой код перестать работать? Да — MAJOR. Нет, но появилось новое — MINOR. Ни того, ни другого — PATCH.

Шаг 2. Автор публикует. Пакет уезжает в реестр (npm, PyPI, crates.io) под этим номером и там замораживается навсегда.

Шаг 3. Ты объявляешь не версию, а диапазон. В этом главная практическая ценность SemVer. Ты пишешь в конфиге не «хочу ровно 1.4.2», а «хочу любую, совместимую с 1.4.2»:

^1.4.2   → от 1.4.2 включительно до 2.0.0 не включая
           (карет: «обновляй, пока не ломают»)
~1.4.2   → от 1.4.2 включительно до 1.5.0 не включая
           (тильда: «только исправления багов»)
1.4.2    → ровно эта, без вариантов

Отдельная ловушка: для нулевого мажора ^0.4.2 означает >=0.4.2 <0.5.0 — карет там ведёт себя как тильда, потому что в 0.x минорная версия и играет роль мажорной.

Шаг 4. Резолвер решает головоломку. У тебя 40 прямых зависимостей, у них свои. Программа-резолвер ищет набор конкретных версий, удовлетворяющий всем диапазонам сразу. Классическая проблема здесь — ромбовидная зависимость (diamond dependency): библиотека A требует C версии ^1.0, библиотека B требует C версии ^2.0. Одна общая версия C, удовлетворяющая обоим, не существует. Дальше или конфликт, или менеджер ставит две копии C рядом (npm умеет, Python — почти нет).

Шаг 5. Lock-файл фиксирует результат. package-lock.json, Cargo.lock, poetry.lock, uv.lock — это протокол решения: какие именно версии победили, с хешами. Благодаря ему сборка на твоей машине, у коллеги и на сервере даёт побайтово одно и то же.

Ты пишешь:      "lib": "^1.4.2"      ← намерение, диапазон
Резолвер решил: lib 1.7.3            ← факт, записан в lock
Через месяц:    lib 1.9.0 вышла      ← подтянется при обновлении,
                                       но не сама по себе

Диапазон без lock-файла — это лотерея

Если в репозитории лежит ^1.4.2 и нет lock-файла, сборка сегодня и сборка через неделю поставят разные версии. Проект «ломается сам по себе», хотя код никто не трогал. Lock-файл обязан лежать в репозитории — это не мусор и не артефакт сборки.

Где встречается в обычной жизни

Где встречается в IT и бизнесе

Кто пользуется

Альтернативы и конкуренты

CalVer (календарное версионирование). Ubuntu 24.04, JetBrains 2026.1, pip 26.1.
Плюсы: сразу видно возраст релиза; отлично для продуктов с фиксированным графиком выпуска; не надо спорить, ломающее ли это изменение.
Минусы: номер ничего не говорит о совместимости; автоматика обновлений на него не опирается.

Sequence-based / маркетинговое. Chrome 145, Firefox 138, Windows 11.
Плюсы: понятно человеку — больше значит новее; никаких споров внутри команды.
Минусы: для машин бесполезно; заставляет держать отдельную, невидимую снаружи схему совместимости.

Хеш коммита как версия. Встречается во внутренних сервисах: build-a1b2c3d.
Плюсы: абсолютно точен, всегда однозначно указывает на код.
Минусы: невозможно сравнить «больше/меньше», нельзя выразить диапазон, нельзя понять, что новее.

ZeroVer — «вечная 0.x». Полушутка-полудиагноз (есть сайт 0ver.org, собирающий примеры): проект годами не выпускает 1.0, чтобы не брать на себя обязательств. Terraform прожил в 0.x до 2021 года.
Плюсы: честно, если API правда нестабилен.
Минусы: если проектом уже пользуются в проде, это не честность, а уклонение — пользователи всё равно рассчитывают на стабильность.

Когда НЕ стоит использовать

Когда у продукта нет API. Для приложения с интерфейсом для людей SemVer не даёт ничего: пользователь не «интегрируется», он просто нажимает кнопки. Календарная схема или сквозной номер подходят лучше, потому что отвечают на реальный вопрос пользователя — «у меня свежее?».

Когда ты не готов держать обещание. SemVer хуже отсутствия схемы, если объявить его и нарушать. Люди настроят ^1.0.0 в расчёте на автоматическое безопасное обновление, и ломающее изменение прилетит им в прод ночью, без единого предупреждения. Ложное обещание опаснее честного отсутствия обещания.

Когда «ломающее изменение» невозможно определить. Тут в дело вступает закон Хайрама (Hyrum's Law, сформулирован инженером Google Хайрамом Райтом): при достаточном количестве пользователей API неважно, что записано в контракте — от любого наблюдаемого поведения системы кто-нибудь да зависит. Порядок ключей в ответе, текст сообщения об ошибке, скорость выполнения. Формально ты чинишь баг и ставишь PATCH — а у кого-то на этом баге держался прод.

Где встретилось у меня

Третьего сентября утром автоматика, которая пишет записи в журнал проектов, отработала вхолостую: тридцать три вызова подряд вернули одно и то же — установленная версия CLI не поддерживает запрошенную модель, требуется версия свежее. Между установленной и требуемой лежала почти сотня патч-релизов при неизменных мажоре и миноре.

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

Вторая половина дня прошла в сверке рекламных кампаний с базой проектов: около 1700 групп и 13 тысяч объявлений, чтение через API без единой записи. Там всплыла родственная беда — расхождение между двумя источниками правды, которое накопилось незаметно, потому что связи только добавлялись и никогда не снимались. По сути тот же класс проблем, что и версии зависимостей: состояние, которое дрейфует, пока его специально не сверишь.

Что сделать на этой неделе

Найди в своих проектах места, где версия зависимости записана диапазоном без lock-файла, и зафиксируй их. А в любом коде, который ходит во внешний API, добавь понятную обработку ответа «твоя версия устарела»: сообщение должно называть текущую версию, требуемую и способ обновиться. Ошибка, которая сама говорит, что делать, экономит часы.

Связанные понятия

Литература и источники

Краткое резюме