Инженерия

Semantic versioning и ваш changelog

5 мин чтения

Semantic versioning говорит вызывающей стороне, насколько больно может быть от релиза, ещё до того, как она прочитает хоть одну запись changelog. Переход с 2.4.1 на 2.5.0 говорит: новая возможность, ничего не сломано. Переход с 2.5.0 на 3.0.0 говорит: прочитай эту запись перед обновлением. Changelog и номер версии должны утверждать одно и то же в двух форматах, и большая часть трения между ними всплывает именно тогда, когда они расходятся — а это случается чаще, чем предполагает спецификация.

Что на самом деле обещает каждая цифра версии?

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

СкачокЗначениеЗапись должна звучать как
MAJOR (1.x.x -> 2.0.0)Несовместимое изменение«Требуется действие перед обновлением»
MINOR (1.2.x -> 1.3.0)Новая, совместимая возможность«Доступно уже сейчас, больше ничего не изменилось»
PATCH (1.2.3 -> 1.2.4)Совместимое исправление«Теперь ведёт себя так, как задокументировано»

Таблица работает и в обратную сторону как проверка: если запись не читается как её строка, значит либо номер версии неверен, либо запись занижает или завышает то, что произошло на самом деле.

Что считается несовместимым для целей версионирования?

Тот же тест, который решает, место ли чему-то в changelog API: могла бы корректная вызывающая сторона, написанная против старого поведения и с тех пор не тронутая, повести себя иначе из-за этого изменения. Что такое несовместимое изменение, и как его выпустить разбирает решение целиком, включая случаи, которые выглядят несовместимыми, но не являются таковыми, и те, что выглядят мелкими, но не являются. Коротко для версионирования: если ответ да, скачок — MAJOR, независимо от того, сколько кода изменение на самом деле затронуло внутри. Номера версий отслеживают последствие для вызывающей стороны, а не усилие команды.

Как запись changelog должна соответствовать скачку версии?

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

## 3.0.0 (2026-09-07)

### Changed
- **BREAKING:** `GET /reports` теперь возвращает суммы как целые числа
  в наименьшей денежной единице (копейках) вместо дробей. Обновите
  код, читающий `amount` напрямую.

## 2.9.0 (2026-09-01)

### Added
- Отчёты теперь можно фильтровать по `status`.

## 2.8.4 (2026-08-28)

### Fixed
- `GET /reports?status=` возвращал пустую страницу вместо 400 для
  неизвестного статуса.

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

Применяется ли правило о breaking changes так же и до 1.0.0?

Нет, и именно отсюда берётся большая часть путаницы «было ли это на самом деле breaking». SemVer прямо говорит, что нулевая мажорная версия, 0.y.z, предназначена для начальной разработки: всё может измениться в любой момент, и публичный API не следует считать стабильным. Скачок с 0.4.0 на 0.5.0 может нести breaking change, не нарушая спецификацию, потому что гарантия мажорной версии начинает действовать только с выпуска 1.0.0. Запись changelog по-прежнему обязана читателям той же честностью о том, что сломалось; меняется только то, что сам номер версии до 1.0.0 не является сигналом, на который стоит полагаться.

А если ваш продукт не выпускает дискретные версии?

Большинство SaaS-продуктов деплоятся непрерывно и никогда не показывают номер версии вызывающей стороне, что не отменяет потребность в этой дисциплине — только цифру, которая обычно бы её несла. Запись changelog должна проделать всю работу сама: чётко сказать, несовместимо ли изменение, дополняющее оно, или это исправление — теми же тремя словами, которыми пользуется semantic versioning, даже без поля версии, к которому их привязать. Некоторые команды держат чисто внутреннюю версию только для того, чтобы привязать записи changelog к чему-то, на что можно сослаться, никогда не показывая её напрямую вызывающей стороне.

Как это применяется именно к changelog API?

Строже, чем почти где-либо ещё, потому что вызывающие стороны API — это код, а не люди, способные пожать плечами на неожиданное изменение. Changelog API: что публиковать и кто это читает разбирает полную форму этого документа; дисциплина версионирования здесь — то, что держит честными его разделы breaking и additive. API, предлагающий несколько версий одновременно — например, v1 и v2, обслуживаемые параллельно во время окна миграции — эффективно применяет semantic versioning в масштабе всего интерфейса, а не одного пакета, и тот же трёхсловный словарь всё ещё применяется к каждой записи.

Что Keep a Changelog говорит о версионировании?

Он напрямую связывается по имени с semantic versioning и рекомендует тот же словарь категорий, которым пользуется эта статья: Added, Changed, Deprecated, Removed, Fixed, Security. Keep a Changelog на практике разбирает, как принять эту спецификацию, включая места, где команды обычно от неё отклоняются. Пересечение не случайно: обе спецификации пытаются решить одну и ту же проблему с противоположных концов — одна стандартизирует номер версии, другая — запись, которая его объясняет.

FAQ

Нужен ли каждой записи changelog номер версии? Если продукт выпускает версии — да, потому что цифра позволяет читательнице сразу перейти к «насколько это меня касается», не читая сначала запись. Если продукт деплоится непрерывно без поля версии, формулировка записи должна нести этот сигнал сама.

В чём разница между скачком MAJOR и записью о несовместимом изменении? Они должны описывать одно и то же событие двумя способами. Номер версии — сигнал, читаемый машиной (инструменты вызывающей стороны могут на него реагировать); запись changelog — объяснение, читаемое человеком, что конкретно изменилось.

Может ли релиз PATCH быть несовместимым? По определению не должен. Если такой всё же вышел, не редактируйте и не перетегируйте опубликованную версию: SemVer FAQ советует выпустить новую версию, восстанавливающую совместимость, или новую MAJOR, если поломка остаётся, и задокументировать проблемную версию, чтобы пользователи знали, что её надо пропустить.

Нужен ли чисто внутренним изменениям скачок версии? Нет. Semantic versioning отслеживает публичный интерфейс. Рефакторинг без наблюдаемого эффекта для вызывающей стороны не нуждается ни в скачке, ни в записи changelog, даже если внутри это была значительная инженерная работа.


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

По теме на changeloop: Генератор changelog, Документация для разработчиков

changeloop
Команда, которая делает changelog, замыкающий цикл. Пользователи о чём-то просят, ваша команда это делает, тот, кто просил, узнаёт об этом.