Changelog
Changelog — це впорядкований, зрозумілий людині перелік помітних змін у кожній версії програми, написаний для того, хто вирішує, чи оновлюватися. Саме через цього читача вивантаження повідомлень комітів, хай яке повне, changelog не є. Сторінка починається з готового changelog; далі — документи, які з ним плутають, що обіцяє номер версії, як написати запис, депрекація, як далеко чесно заходить автоматизація, чотири релізи, де проблемою був рядок changelog, і де практика вироджується.
- Написаний для
- Того, хто вирішує оновитися
- Формат
- Keep a Changelog 1.1.0
- Схема версій
- SemVer 2.0.0, з 2013 року
Changelog на одному екрані — готовий
Більшість тих, хто шукає changelog, хоче побачити хороший. Ось один — для невеликої бібліотеки всередині інтернет-магазину, що проходить крізь сторінки розділів «Декомпозиція» і «Процес релізу». orders-client — пакет, через який чотири команди читають замовлення: вебмагазин, консоль підтримки, листи-підтвердження замовлень і нічне вивантаження для аналітики. Кожна команда сама вирішує, коли оновлюватися, — і саме для такого читача існує changelog. На екран уміщається лише частина файлу: шапка, розділ Unreleased і чотири версії. Наступні розділи посилаються на рядки за номерами.
1.Шапка — що це за файл і що він обіцяє
- Усі помітні зміни
orders-clientзаписано тут, найновіші першими. Формат — Keep a Changelog 1.1.0 - Версії відповідають Semantic Versioning 2.0.0. Публічний APIApplication Programming Interface — контракт між програмамиПравила, за якими одна програма звертається до іншої: які запити можна надіслати, у якому вигляді й що прийде у відповідь. Для того, хто звертається, це контракт — усе, що за ним, лишається чорною скринькою, і саме тому другий бік можна переписати, не зламавши вас.Докладніше в статті →Усі терміни — те, що перелічено в
docs/api.md: експортовані функції, поля повернених об’єктів і коди помилок. Усе інше може змінитися в будь-якому релізі - Політика депрекації: усе задепрековане працює до наступної мажорної версії — і щонайменше шість місяців після релізу, який це задепрекував
- Усі помітні зміни
2.[Unreleased]
- Added —
order.reorderSource:history,saved-basketабоnull— звідки почали повторне замовлення. У замовлень, оформлених до 20 жовтня, завждиnull - Deprecated —
getOrders({ customerId }). ВикористовуйтеgetCustomerOrders(customerId)з рядка 3.1. Буде видалено в 3.0.0 — і не раніше ніж через шість місяців після цього релізу. Виклик пише одне попередження на процес, а ендпоінт за ним відповідає заголовкамиDeprecationіSunset
- Added —
3.[2.7.0] — 2026-09-15
- Added —
getCustomerOrders(customerId), що проходить сторінками історію будь-якої довжини.getOrdersмовчки зупинявся на 100 замовленнях - Added —
address.postcodeз тим самим значенням, що йaddress.zip - Deprecated —
address.zip. Читайте натомістьaddress.postcode.zipлишається до 3.0.0 і щонайменше до 15 березня 2027 року; перше читання в процесі пише попередження - Security — email клієнта більше не потрапляє в debug-лог (
DEBUG=orders-client). Оновіться негайно, якщо debug-логування хоч раз вмикали в production
- Added —
4.[2.6.2] — 2026-08-27
- Fixed —
order.placedAtзнову в UTCCoordinated Universal Time — світовий еталонний часСтандарт часу, у якому зазвичай ведуть годинники й логи, — без часових поясів і переходу на літній час. Він додає високосні секунди, щоб не відставати від обертання Землі, тому час GPS, який цього не робить, нині випереджає його на 18 секунд.Усі терміни, як і задокументовано. 2.6.1 повертала локальний час сервера
- Fixed —
5.[2.6.1] — 2026-08-26 [YANKED]
- Fixed —
order.totalтепер враховує знижку за ваучером. Не використовуйте цю версію: вона ще й перевелаplacedAtна локальний час, і 212 пізніх вечірніх замовлень потрапили в нічному вивантаженні не в той день. Відкликано наступного ранку; використовуйте 2.6.2
- Fixed —
6.[2.0.0] — 2026-03-02
- Removed —
getOrderLines(orderId). Рядки замовлення тепер вorder.lines. Див. «Перехід на 2.0», крок 1 - Changed — гроші тепер ціле число найменших одиниць валюти:
total: 4999, а не"49.99". Кожне місце, що форматує чи додає ціни, треба змінити. Див. «Перехід на 2.0», крок 2
- Removed —
7.Посилання
- Кожен заголовок версії веде на свій diff —
[2.7.0]: …/compare/v2.6.2...v2.7.0, — а[Unreleased]порівнює останній тег ізmain
- Кожен заголовок версії веде на свій diff —
Git log — не changelog, і реліз-ноти теж
Слово має два значення, і це половина плутанини. GNU Coding Standards вимагають файл ChangeLog, що фіксує кожну зміну кожного файлу коду, — щоб «ті, хто розслідуватиме баги в майбутньому», могли їх простежити. Нині стандарти приймають замість нього журнал системи контролю версій. Ті самі стандарти вимагають другий файл, NEWS: «перелік видимих користувачеві змін, вартих згадки», найновіші першими. Те, що Keep a Changelog називає changelog, — це другий файл під назвою першого. Олів’є Лакан уперше опублікував цю конвенцію 2014 року, і вона визначає документ як «впорядкований хронологічний перелік помітних змін для кожної версії проєкту».
Changelog, версія 2.7.0
- Added —
getCustomerOrders(customerId);getOrdersзупинявся на 100 замовленнях - Deprecated —
address.zip; читайтеaddress.postcode - Security — email-адреси більше не потрапляють у debug-лог
Коміти за ним
- feat: paginate history
- feat(address): add postcode
- fix: redact pii in debug
- refactor: split http module
- Merge branch 'feat/postcode'
Прочитайте праву колонку очима однієї з чотирьох команд-споживачів. Ніщо в ній не каже, що zip зникне, — а це єдине, що створює роботу. Третій коміт — виправлення безпеки, записане як звичайний fix. А останні два взагалі нічого не кажуть нікому поза командою.
Чотири документи, чотири читачі
Більшість поганих changelog — це один файл, що намагається бути кількома. Чотири документи нижче відрізняються тим, хто їх читає, коли ця людина їх відкриває і що їй треба знайти.
| Документ | Читач | Коли відкривають | Що містить | Коли пишуть |
|---|---|---|---|---|
| Журнал комітів | Команда, через рік | Коли шукають зміну, що спричинила баг | Кожен крок по порядку, словами команди | З кожною зміною, її автором |
| Changelog | Розробник, що від вас залежить | Перед оновленням — або після того, як воно щось зламало | Кожна помітна зміна, по рядку, згруповано за типом | З кожною зміною, редагують на релізі |
| Реліз-ноти | Користувачі, що не стежили за роботою | Коли реліз анонсують | Кілька змін, вартих анонсу, з поясненням | На релізі — продукт чи маркетинг |
| Гайд з міграції | Той, хто посеред мажорного оновлення | Коли збірка щойно впала | Для кожної ламкої зміни: до, після і як перевірити | З ламкою зміною, до виходу мажорної версії |
Реліз повторного замовлення зі сторінки про планування релізу дає три з них. Рядок 2.1 вище — рядок changelog для чотирьох команд-споживачів. Клієнти натомість отримують реліз-ноту: «Повторіть минуле замовлення з історії двома дотиками. Усе, чого немає в наявності, названо — з пропозицією заміни». Це рядок 1.4 плану релізу. А коли 3.0.0 прибере zip, гайд з міграції отримає крок зі старим читанням, новим читанням і пошуком, що знаходить кожне використання, яке лишилося.
Номер версії — це обіцянка
Semantic Versioning перетворює три числа на контракт сумісності. Його написав Том Престон-Вернер; версію 1.0.0 специфікації позначили тегом у вересні 2011 року, а 2.0.0, чинну досі, — у червні 2013-го. MAJOR зростає за несумісних змін публічного API, MINOR — за нової зворотно сумісної функціональності, PATCH — за зворотно сумісних виправлень. Річ не в охайності. Читач може вирішити, чи безпечне оновлення, не читаючи diff, а менеджер пакетів може вирішити це замість нього.
Правило, на якому все тримається: якщо наявним користувачам треба щось змінити, щоб усе далі працювало, це підняття MAJOR — хай яким дрібним це здається зсередини. Перейменування поля, суворіша валідація, обов’язковий параметр замість необов’язкового і зміна значення за замовчуванням — усе це ламкі зміни. Випустити одну з них патчем — саме так залежність заробляє собі репутацію.
| Зміна | Підняття | Чому |
|---|---|---|
| Додати функцію чи необов’язкове поле (3.1, 3.2) | MINOR | Ніщо, що працювало, не перестає працювати |
| Позначити щось задепрекованим (3.3) | MINOR | Цього вимагає специфікація, пункт 7 |
| Змусити код робити те, що каже документація (4.1) | PATCH | Якщо тільки на баг не покладається багато користувачів, — тоді, словами FAQ, «судіть на власний розсуд» |
| Видалити чи перейменувати будь-що публічне (6.1) | MAJOR | Кожне використання перестає компілюватися або починає падати |
| Змінити тип чи формат (6.2) | MAJOR | Код і далі запускається — і рахує не те |
| Зробити параметр обов’язковим чи суворіше перевіряти вхід | MAJOR | Виклики, які приймали, тепер відхиляють |
| Змінити значення за замовчуванням | MAJOR | Код, що його ніколи не задавав, тепер поводиться інакше, — а інструменти записують це як виправлення |
| Відмовитися від старого рантайму, наприклад версії Node.js | MAJOR | За домовленістю — специфікація Conventional Commits бере саме це за приклад |
Змінити спосіб завантаження пакета — ES-модуль, мапа exports | MAJOR | Імпорти, що працювали, перестають розв’язуватися; див. is-promise нижче |
| Оновити залежність без зміни публічного API | PATCH або MINOR | FAQ: сумісно, а яке з двох — вирішує причина оновлення |
Нуль, пререлізи та метадані збірки
Усе, що починається з 0, явно поза обіцянкою: під час 0.y.z, каже пункт 4, «будь-що МОЖЕ змінитися будь-коли». Це чесно, поки дизайн ще рухається, і нечесно, коли від нього вже залежать. FAQ самої специфікації каже прямо: «Якщо вашою програмою користуються в production, вона, ймовірно, вже має бути 1.0.0». Проєкт, що чотири роки сидить на 0.x зі споживачами в production, використовує нуль, щоб уникнути зобов’язання.
Пререліз позначають дефісом — 3.0.0-rc.1 — і він сортується перед релізом, якому передує, тож менеджер пакетів, що просить 3.0.0 чи вище, не візьме його випадково. Метадані збірки позначають плюсом — 3.0.0+build.482 — і при порівнянні версій їх повністю ігнорують. Тож це правильне місце для хешу коміту й неправильне — для будь-чого, що треба знати користувачеві. А випущену версію ніколи не змінюють: якщо 2.6.1 виявилася зламаною, виправлення — це 2.6.2, а 2.6.1 лишається у файлі з позначкою, як у групі 5.
Контракт — і все інше, від чого залежать люди
SemVer може обіцяти лише те, що каже публічний API. Гайрам Райт, тоді в Google, сформулював проблему одним реченням, яке Тайтус Вінтерс згодом назвав законом Гайрама: «Коли в API достатньо користувачів, неважливо, що ви обіцяєте в контракті: від кожної помітної поведінки вашої системи хтось залежатиме». Порядок результатів, тривалість виклику, точне формулювання помилки — чийсь код це читає.
Менеджери пакетів роблять це гострішим. У npm залежність, записана як ^2.6.0, приймає будь-яку 2.x.y від 2.6.0 і вище, тож нова minor-версія доходить до кожного споживача на наступному чистому встановленні — і ніхто перед тим не читає changelog. lockfileточні версії кожної залежностіФайл, який менеджер пакетів пише поряд зі списком залежностей і в якому записано точну версію кожного встановленого пакета — зокрема тих, що підтягнули ваші залежності. Закомічений у репозиторій, він робить кожне встановлення однаковим; без нього дві збірки того самого коміту можуть виконувати різний код.Усі терміни тримає стару версію, доки її хтось не оновить, — це відкладає сюрприз, але не скасовує його. Тому MINOR, що щось ламає, розходиться далі й швидше, ніж будь-коли зміг би MAJOR.
Інші схеми нумерації — і що обіцяє кожна
| Схема | Приклад | Число каже | Сумісність обіцяє |
|---|---|---|---|
| Semantic Versioning | Бібліотеки й пакети: 2.7.0 | Який це вид зміни | Саме число |
| Календарне версіонування | Ubuntu 24.04 (рік і місяць); pip 25.2 (рік і реліз) | Скільки йому років | Окрема політика — pip депрекує щонайменше на шість місяців |
| Датовані версії API | Stripe: 2026-09-30.endive | Яку поведінку ви замовили | Закріплення: кожен акаунт лишається на своїй версії, доки не оновиться |
| Одне зростаюче число | Chrome і Firefox — кожні чотири тижні | Лише порядок релізів | Процеси депрекації вебможливостей, а не число |
| Мажорна версія в шляху імпорту | Модулі Go: example.com/lib/v2 | Яка це несумісна гілка | Шлях: «Якщо старий і новий пакет мають однаковий шлях імпорту, новий мусить бути зворотно сумісним» |
Схема Stripe варта другого погляду, бо відповідає на питання, яке SemVer лишає споживачеві: коли я беру ламку зміну? Кожен акаунт закріплено за версією API, що була чинною, коли він почав роботу, а запит може попросити іншу заголовком Stripe-Version. До 2017 року Stripe так випустила «майже сотню зворотно несумісних оновлень», нікого не зламавши без його згоди. Починаючи з 2024-09-30.acacia, щомісячні версії не мають ламких змін, а мажорний реліз із ламкими змінами виходить двічі на рік.
Як написати запис, за яким можна діяти
Keep a Changelog групує кожен реліз під шістьма заголовками. Однакові шість скрізь означають, що читач може одразу шукати потрібний — зазвичай Removed і Changed, бо саме вони створюють роботу.
| Заголовок | Для чого | Читач питає | На цій сторінці |
|---|---|---|---|
| Added | Нові можливості | Чи з’явилося щось, що я тепер можу? | 2.1, 3.1, 3.2 |
| Changed | Наявна поведінка, що стала іншою | Чи поводитиметься мій поточний код інакше? | 6.2 |
| Deprecated | Ще працює, буде видалено | З чого і до коли треба перейти? | 2.2, 3.3 |
| Removed | Зникло в цій версії | Що зламається одразу після оновлення? | 6.1 |
| Fixed | Виправлені баги | Чи можна прибрати мій обхідний шлях? | 4.1, 5.1 |
| Security | Закриті вразливості | Чи мене це стосується і як терміново оновлюватися? | 3.4 |
Решта конвенції механічна, і її варто дотримуватися точно, бо саме вона дозволяє читачеві щось знайти. Її керівні принципи: changelog — для людей, а не для машин; запис для кожної версії без винятку; зміни одного типу згруповано; на версії й розділи можна послатися; найновіша версія — першою; дату релізу кожної версії показано; і сказано, чи дотримується проєкт Semantic Versioning. Дати — за ISO 8601: 2026-09-15, а не 15/09/26, що читається як два різні дні залежно від країни читача. Розділ Unreleased стоїть угорі, а відкликана версія лишається у файлі з гучною позначкою [YANKED].
Вісім записів, як їх пишуть команди, — і переписані
| Як написано | Чого читач не дізнається | Переписано |
|---|---|---|
| «Виправлення помилок і покращення» | Взагалі нічого | Рядок на кожну зміну, як у 4.1 |
| «Оновлено перевірку токенів» | Чи це їх стосується | Наслідок і умова — див. приклад нижче |
| «Відрефакторено HTTP-шар» | Помічати нічого | Видалити рядок |
| «Виправлено #4812» | Що було зламано | Назвіть симптом: «getOrders мовчки зупинявся на 100 замовленнях», як у 3.1 |
| «Видалено застарілий ендпоінт» | Який саме і що натомість | Назвіть його, заміну й крок гайду, як у 6.1 |
| «Покращено продуктивність» | Де саме і наскільки | «getCustomerOrders для 500 замовлень: 280 мс, було 1,9 с» |
«Задепрековано zip» | Що використовувати натомість і до коли | Заміна, версія видалення, найраніша дата, як у 3.3 |
| «Виправлення безпеки» | Чи їх це стосується і наскільки терміново | Що витікало чи було можливим, за якого налаштування і кому оновлюватися, як у 3.4 |
Дві звички роблять більшу частину решти роботи. Наповнюйте розділ Unreleased у міру появи змін, щоб кожен запис писав автор зміни, поки вона свіжа. І формулюйте кожен рядок як наслідок для читача, а не як правку в коді. Changelog, зібраний напередодні релізу, завжди виходить худим, бо той, хто його збирає, відновлює місяць за темами комітів.
Депрекація — те, що купує довіру
Removed — заголовок, що створює роботу; Deprecated — заголовок, що дає людям час її зробити. Депрекація — це обіцянка з трьох частин, а повідомлення без будь-якої з них є скаргою на власний код.
- Що зникає — названо достатньо точно, щоб читач міг пошукати це у власному коді:
address.zip, а не «старі поля адреси». - Що використовувати натомість — і заміна доступна вже, а не обіцяна на потім: рядок 3.2 випустив
postcodeу тому самому релізі, що задепрекувавzip. - Коли це прибиратимуть — версією й найранішою датою, а не порою року: «3.0.0 і не раніше 15 березня 2027 року».
SemVer вбудовує цю послідовність. Пункт 7 каже, що minor-реліз МУСИТЬ вийти, коли щось публічне позначають задепрекованим, а FAQ просить «щонайменше один minor-реліз, що містить депрекацію», перед мажорним, який її прибирає. Keep a Changelog формулює мету з боку читача: «Має бути можливо оновитися до версії, яка перелічує депрекації, прибрати задепрековане, а тоді оновитися до версії, де депрекації стали видаленнями». Рядок 1.3 додає нижню межу в часі, бо споживач, що оновлюється двічі на рік, інакше може зустріти депрекацію й видалення за один крок.
| Проєкт | Сигнал там, де працює код | Мінімум до видалення |
|---|---|---|
| Python (PEP 387) | DeprecationWarning під час виконання | Дві minor-версії; бажано п’ять років. distutils: задепрековано в 3.10, видалено в 3.12 |
| Node.js | Чотири види з номерами від DEP0001: лише в документації, для застосунку, під час виконання, кінець життя | Фіксованого вікна немає; депрекація піднімається видами до видалення |
| Kubernetes | З v1.19 — попередження на кожен запит до задепрекованого API і метрика того, хто його досі викликає | Beta: 9 місяців або 3 релізи, що довше. GA: ніколи в межах мажорної версії |
| pip | Попередження, коли поведінкою користуються | Шість місяців; три, якщо двоє мейнтейнерів погодилися пришвидшити |
| Stripe | Не потрібен: акаунт лишається на закріпленій версії | Доки клієнт не оновиться |
| Будь-яке HTTP API | Заголовки відповіді Deprecation (RFC 9745, 2025) і Sunset (RFC 8594, 2019) | Те, що публікує провайдер, — заголовки несуть дати |
Депрекація HTTP-ендпоінта
Ендпоінт за рядком 2.2, GET /orders?customer=, може казати про це в кожній відповіді. Deprecation: @1789430400 дає момент депрекації, 15 вересня 2026 року, у секундах Unix після @. Sunset: Mon, 15 Mar 2027 00:00:00 GMT дає найраніший момент, коли він може перестати відповідати, — у старішому форматі HTTP-date. Заголовок Link з rel="deprecation" веде на сторінку з поясненням переходу. RFCRequest for Comments — нумерований стандарт інтернетуНумерований документ, який публікує IETF — організація, що стандартизує інтернет-протоколи; HTTP, TLS і JSON визначено саме в RFC. Опублікований RFC ніколи не змінюється — виправлення виходить новим RFC з новим номером, тому «RFC 9110» називає один точний текст.Усі терміни 9745 додає два правила, які варто знати. Депрекація «не змінює жодної поведінки ресурсу», а дата Sunset не може бути ранішою за дату Deprecation.
Заголовок позначає ресурс, а не поле, тож рядок 3.3 він оголосити не може. Про address.zip повідомляють changelog і документація, а бібліотека пише попередження, коли процес уперше його читає. Саме з цього попередження команда й дізнається, хто досі від нього залежить.
Як далеко чесно заходить автоматизація
Conventional Commits 1.0.0, опублікована 2019 року й виросла з конвенції комітів проєкту Angular, стандартизує перший рядок повідомлення коміту так, щоб його міг прочитати інструмент: тип, необов’язкова область, необов’язковий ! для ламкої зміни, далі опис — «feat(address): add postcode», «fix!: require a currency on every price». Ламку зміну можна також оголосити у футері, що починається з BREAKING CHANGE:, і це краще місце, коли потрібне речення пояснення. Інші типи, крім feat і fix, дозволені і, за специфікацією, «не мають неявного впливу на Semantic Versioning».
| Коміт | Означає | Потрапляє під |
|---|---|---|
| fix: | PATCH | Fixed |
| feat: | MINOR | Added |
| ! або BREAKING CHANGE: | MAJOR | Changed або Removed |
| chore:, docs:, refactor:, test: | Нічого | Зазвичай нікуди — жодного ефекту, який побачить користувач |
| Deprecated, Security | Власного типу немає | Туди їх має поставити людина, — коміт у порівнянні вище був звичайним fix |
Це справді допомагає. Інструмент може вивести наступний номер версії й зібрати чернетку під правильними заголовками, а це два найнудніші кроки. Чого він не може — вирішити, чи є зміна ламкою, бо це залежить від того, як люди користуються продуктом, а це знання живе поза репозиторієм. Коміт із міткою fix:, що змінює значення за замовчуванням, є ламким, — а інструмент цілком упевнено назве його патчем.
| Інструмент | Читає | Створює | Де вирішує людина |
|---|---|---|---|
| semantic-release | Повідомлення комітів | Версію, нотатки й опублікований пакет — без кроку людини | Лише в кожному повідомленні коміту; README каже, що він «прибирає безпосередній зв’язок між людськими емоціями й номерами версій» |
| release-please | Conventional Commits | Реліз-pull request, що тримає changelog і підняття версії актуальними | Редагування й злиття цього pull request |
| Changesets | Невеликий Markdown-файл на зміну: підняття й короткий опис | Changelog і нові версії | У pull request, автором, на рев’ю разом із кодом |
| towncrier | «Фрагменти новин» — файл на зміну, написаний для кінцевих користувачів | Файл новин на релізі; ним користуються pip, pytest і Twisted | У pull request, як у Changesets |
Останні два відповідають на проблеми, які порушили попередні розділи. Запис пише той, хто розуміє зміну, поки вона свіжа, і його переглядають у тому самому pull requestзміна, запропонована на рев’юЗапит на злиття однієї гілки в іншу, де зміну переглядають, обговорюють і перевіряють пайплайном, перш ніж вона потрапить у головну гілку; у GitLab те саме зветься merge request. Його ціна — очікування: pull request, що днями чекає на рев’ю, і породжує довгоживучі гілки та болісні злиття.Докладніше в статті →Усі терміни, що й код, — тож рецензент, який бачить зміну значення за замовчуванням, бачить і рядок, що називає її патчем. А оскільки кожна зміна приносить власний файл, немає єдиного CHANGELOG.md, який редагує кожна гілка і на якому конфліктує кожне злиття. README towncrier пояснює причину прямо: він доносить новини так, як «зручно тим, хто їх чує, а не тим, хто їх пише».
Чотири релізи, де проблемою був рядок changelog
Кожен кейс нижче задокументував сам проєкт. У жодному з них код не був усією історією. Щоразу не дійшло до читача число, рядок чи сигнал, які мали б дійти.
| Кейс | Що сталося | Чого потребував читач | Рядки |
|---|---|---|---|
| is-promise, 25 квітня 2020 | Minor-реліз змінив спосіб завантаження пакета; популярні інструменти ламалися на свіжих встановленнях три години | Число MAJOR | 1.2, 6.2 |
| Kubernetes 1.16, 18 вересня 2019 | API, задепрековані майже два роки, перестали відповідати; багато хто вперше про це почув саме тоді | Сигнал там, де працює код, а не лише рядок у реліз-нотах | 2.2, 3.3 |
| Kubernetes і dockershim, грудень 2020 | Правильний рядок депрекації прочитали як «Kubernetes відмовляється від Docker» | Кого це стосується — і окремий документ для всіх, кого ні | 3.4 |
| Log4j, грудень 2021 | Чотири релізи за вісімнадцять днів, кожен безпечний — до наступного | Уражені й виправлені діапазони для кожної підтримуваної гілки | 3.4 |
is-promise, 2020 — minor, що був major
is-promise — бібліотека в один рядок: функція, що каже, чи є значення промісом. За тиждень до 25 квітня 2020 року її завантажили близько 11,7 мільйона разів — майже завжди як залежність чогось іншого. Того дня о 15:03 UTC мейнтейнер опублікував 2.2.0, перший реліз із вересня 2015-го. Він додав точку входу ES-модуля — "type": "module" і мапу exports у package.json, — і мапа виявилася хибною. Кожен проєкт, що залежав від ^2.1.0, отримав новий minor на наступному чистому встановленні. Свіжовстановлені Create React App, інструмент командного рядка Angular та інструменти Firebase перестали працювати.
2.2.1 вийшла о 17:55, а 2.2.2, уже без exports, — о 18:08: загалом близько трьох годин. Найкорисніше тут — коментар самого мейнтейнера в issue: «Я прочитав документацію, а тоді зробив цю зміну, вважаючи її доволі безпечною». Наступного дня ту саму зміну exports, уже правильно сформовану, опублікували знову — як 3.0.0. Зміна способу завантаження пакета є частиною його публічного API, і число має це казати.
Kubernetes 1.16, 2019 — два роки попереджень, яких ніхто не бачив
Kubernetes 1.16, випущений 18 вересня 2019 року, перестав обслуговувати Deployment, DaemonSet, StatefulSet і ReplicaSet зі старих версій API extensions/v1beta1 та apps/v1beta1/v1beta2. Заміна, apps/v1, була доступна з v1.9, грудень 2017 року. Депрекація була в реліз-нотах, а в липні проєкт опублікував допис-попередження про видалення. Маніфести й Helm-чарти, досі написані під старі версії, після оновлення однаково падали.
Відповідь проєкту у v1.19 (2020) перенесла попередження туди, де працює код. Кожен запит до задепрекованого API тепер повертає заголовок-попередження, який друкує kubectl, з назвою релізу, що перестане його обслуговувати, і заміною. Метрика apiserver_requested_deprecated_apis показує адміністраторам, які задепрековані API досі викликають. Це попередження на процес і заголовок Sunset з рядка 2.2 — у масштабі цілої екосистеми.
dockershim, 2020 — правильний рядок, прочитаний хибно
Реліз-ноти v1.20 у грудні 2020 року задепрекували dockershim — адаптер, через який kubelet використовував Docker Engine як середовище виконання контейнерів. Рядок був точним. Його прочитали як «Kubernetes відмовляється від Docker», і те, що проєкт згодом назвав панікою, перетворилося на «пожежу в соцмережах». Більшості читачів це взагалі не стосувалося, бо образи, зібрані Docker, працюють у кожному середовищі. Проєкт написав «Don’t Panic: Kubernetes and Docker» і FAQ. 2022 року, коли v1.24 прибрав адаптер, допис проєкту сказав прямо: «Ми погано про це повідомили… Це була наша провина».
Рядок changelog був правильним документом для операторів кластерів. Усім іншим потрібен був інший документ — реліз-ноти, що кажуть, кого це стосується, а кого ні, — і він з’явився вже після того, як рядок прочитали хибно. Коли депрекація зачіпає назву, яку люди впізнають, пишіть цей другий документ першим.
Log4j, 2021 — заголовок Security під тиском
CVECommon Vulnerabilities and Exposures — публічний номер відомої вразливостіПублічний ідентифікатор відомої вади безпеки, як-от CVE-2021-44228 для Log4Shell, щоб постачальники, сканери й бюлетені мали на увазі той самий баг. Номер каже, що вада існує; наскільки вона небезпечна саме для вас, залежить від того, чи працює у вас уражена версія і чи відкрита назовні вражена частина.Усі терміни-2021-44228 у Log4j 2 з оцінкою 10,0 дозволяла віддалене виконання коду через рядок, що потрапляв у лог. Версія 2.15.0 з’явилася в Maven Central 9 грудня 2021 року. Далі виявилися нові проблеми, а слідом вийшли 2.16.0 13 грудня, 2.17.0 18 грудня і 2.17.1 27 грудня — кожна «безпечна версія» до наступної. Старші гілки Java отримали власні виправлення: 2.12.x для Java 7 і 2.3.x для Java 6. Три тижні команди по всьому світу знову й знову відкривали одну сторінку, щоб відповісти на два питання: чи це мене стосується і на що переходити?
Сторінка безпеки Apache Logging відповідає на них у сталій формі для кожної вразливості: уражений діапазон, виправлена версія для кожної гілки Java і пом’якшення для тих, хто не може оновитися. Саме такої форми, у меншому масштабі, потребує запис Security. Рядок 3.4 каже, що витікало, за якого налаштування і кому оновлюватися. «Виправлення безпеки» не відповіло б на жодне з двох питань.
Де він вироджується — і чого changelog не може
Перевірка changelog — чи відкриває його хтось під тиском. Після оновлення о 16:40 щось зламалося, і людина, яка ніколи не читала вашого коду, відкриває файл. Менш ніж за хвилину вона має з’ясувати, чи змінила версія, на яку вона перейшла, те, що в неї перед очима. Усе в конвенції — найновіше вгорі, шість сталих заголовків, рядок на зміну, наслідки замість правок — існує, щоб зробити цю хвилину можливою. Ось як вона стає неможливою.
- Згенероване вивантаження комітів, опубліковане без редагування, — його ніхто не читає двічі.
- Ламкі зміни, випущені як minor чи patch, — вони витрачають довіру, яку мало нести число.
- Немає оголошеного публічного API — тож будь-який рефакторинг начебто ламкий, і ніхто цього не розрізнить.
- Роки на 0.x зі споживачами в production — нуль, використаний, щоб ухилитися від зобов’язання.
- Депрекації без заміни чи без дати видалення — за ними ніхто нічого не робить, доки видалення не настане.
- Видалення лише за календарем, поки хтось досі цим користується.
- Записи Security, що не кажуть, кого це стосується, — кожному читачеві вони перетворюють день на розслідування.
- Зламану версію видалено замість позначити [YANKED] — і ті, хто її закріпив, не дізнаються, чому вона зникла.
Коли це застосовувати
Застосовуйте, коли
- Інші люди чи команди залежать від вашого софту й самі вирішують, коли оновлюватися.
- Ви публікуєте бібліотеку, API чи будь-що інше з версіями.
- Підтримці треба знати, до якої версії належить повідомлена проблема.
- Ви депрекуєте щось і потребуєте одного місця, де живуть дати.
Уникайте, коли
- Внутрішній модуль з одним споживачем, що сидить поруч, — повідомлення дешевше.
- Вебзастосунок із безперервним деплоєм, користувачі якого ніколи не обирають версію, — пишіть натомість реліз-ноти.
- Його б генерували й ніколи не редагували — це супровід без читача.
- Реліз-ноти для тих самих читачів уже охоплюють кожну зміну — не ведіть два документи, що дублюються.
Джерела й метод
На чому стоїть ця стаття
- 28 джерел
- 4 задокументовані збої
- Перевірено 13 жовтня 2026 р.
Збої, на яких вчиться стаття
розділ «Чотири релізи, де проблемою був рядок changelog»- Постмортемthen/is-promise — Issue #13: ERR_INVALID_PACKAGE_TARGET (відкривається в новій вкладці)GitHub · 25.04.2020
- Постмортемthen/is-promise — Issue #20: "exports" config (відкривається в новій вкладці)GitHub · 04.2020
- СтаттяVallery Lancey — Deprecated APIs Removed In 1.16: Here’s What You Need To Know (відкривається в новій вкладці)Kubernetes Blog · 18.07.2019
- СтаттяJordan Liggitt — Warning: Helpful Warnings Ahead (відкривається в новій вкладці)Kubernetes Blog · 03.09.2020
- СтаттяDon’t Panic: Kubernetes and Docker (відкривається в новій вкладці)Kubernetes Blog · 02.12.2020
- ПостмортемKat Cosgrove — Dockershim: The Historical Context (відкривається в новій вкладці)Kubernetes Blog · 03.05.2022
- ДокументаціяApache Log4j — Security (відкривається в новій вкладці)Apache Logging Services
- ДокументаціяMaven Central — org/apache/logging/log4j/log4j-core (відкривається в новій вкладці)Maven Central
Практики й походження термінів
формат, номер версії і що таке запис- СтандартOlivier Lacan — Keep a Changelog 1.1.0 (відкривається в новій вкладці)keepachangelog.com · 15.02.2019
- СтандартTom Preston-Werner — Semantic Versioning 2.0.0 (відкривається в новій вкладці)semver.org · 18.06.2013
- СтандартConventional Commits 1.0.0 (відкривається в новій вкладці)conventionalcommits.org · 2019
- ГайдGNU Coding Standards — 6.7 NEWS File (відкривається в новій вкладці)Free Software Foundation
- ГайдGNU Coding Standards — 6.8 Change Logs (відкривається в новій вкладці)Free Software Foundation
- СтаттяHyrum Wright — Hyrum’s Law (відкривається в новій вкладці)hyrumslaw.com
Політики депрекації й версіонування
розділ «Депрекація — те, що купує довіру»- СтандартRFC 9745 — The Deprecation HTTP Response Header Field (відкривається в новій вкладці)IETF · 03.2025
- СтандартRFC 8594 — The Sunset HTTP Header Field (відкривається в новій вкладці)IETF · 05.2019
- СтандартPEP 387 — Backwards Compatibility Policy (відкривається в новій вкладці)Python Software Foundation
- СтандартPEP 632 — Deprecate distutils module (відкривається в новій вкладці)Python Software Foundation
- ДокументаціяNode.js — Deprecated APIs (відкривається в новій вкладці)Node.js
- Документаціяpip — Release process (відкривається в новій вкладці)Python Packaging Authority
- ДокументаціяKubernetes Deprecation Policy (відкривається в новій вкладці)Kubernetes
- СтаттяBrandur Leach — APIs as infrastructure: future-proofing Stripe with versioning (відкривається в новій вкладці)Stripe · 08.2017
- ДокументаціяStripe API Reference — Versioning (відкривається в новій вкладці)Stripe
- ДокументаціяGo Modules Reference (відкривається в новій вкладці)The Go Programming Language
Інструменти, що готують чернетку changelog
розділ «Як далеко чесно заходить автоматизація»- Документаціяsemantic-release (відкривається в новій вкладці)GitHub · semantic-release
- Документаціяrelease-please (відкривається в новій вкладці)GitHub · googleapis
- ДокументаціяChangesets (відкривається в новій вкладці)GitHub · changesets
- Документаціяtowncrier (відкривається в новій вкладці)GitHub · twisted
Дослідження з Claude. Пошук і звірку першоджерел зроблено з Claude (Anthropic). Відбір, текст і перевірку фактів — редакція Anatomy of IT Product.
Було корисно?
Поділіться з тим, хто працює над тією ж задачею.