МенеджментJunior

Changelog

Changelog — це впорядкований, зрозумілий людині перелік помітних змін у кожній версії програми, написаний для того, хто вирішує, чи оновлюватися. Саме через цього читача вивантаження повідомлень комітів, хай яке повне, changelog не є. Сторінка починається з готового changelog; далі — документи, які з ним плутають, що обіцяє номер версії, як написати запис, депрекація, як далеко чесно заходить автоматизація, чотири релізи, де проблемою був рядок changelog, і де практика вироджується.

Написаний для
Того, хто вирішує оновитися
Формат
Keep a Changelog 1.1.0
Схема версій
SemVer 2.0.0, з 2013 року

Changelog на одному екрані — готовий

Більшість тих, хто шукає changelog, хоче побачити хороший. Ось один — для невеликої бібліотеки всередині інтернет-магазину, що проходить крізь сторінки розділів «Декомпозиція» і «Процес релізу». orders-client — пакет, через який чотири команди читають замовлення: вебмагазин, консоль підтримки, листи-підтвердження замовлень і нічне вивантаження для аналітики. Кожна команда сама вирішує, коли оновлюватися, — і саме для такого читача існує changelog. На екран уміщається лише частина файлу: шапка, розділ Unreleased і чотири версії. Наступні розділи посилаються на рядки за номерами.

  1. 1.Шапка — що це за файл і що він обіцяє

    1. Усі помітні зміни orders-client записано тут, найновіші першими. Формат — Keep a Changelog 1.1.0
    2. Версії відповідають Semantic Versioning 2.0.0. Публічний — те, що перелічено в docs/api.md: експортовані функції, поля повернених об’єктів і коди помилок. Усе інше може змінитися в будь-якому релізі
    3. Політика депрекації: усе задепрековане працює до наступної мажорної версії — і щонайменше шість місяців після релізу, який це задепрекував
  2. 2.[Unreleased]

    1. Added — order.reorderSource: history, saved-basket або null — звідки почали повторне замовлення. У замовлень, оформлених до 20 жовтня, завжди null
    2. Deprecated — getOrders({ customerId }). Використовуйте getCustomerOrders(customerId) з рядка 3.1. Буде видалено в 3.0.0 — і не раніше ніж через шість місяців після цього релізу. Виклик пише одне попередження на процес, а ендпоінт за ним відповідає заголовками Deprecation і Sunset
  3. 3.[2.7.0] — 2026-09-15

    1. Added — getCustomerOrders(customerId), що проходить сторінками історію будь-якої довжини. getOrders мовчки зупинявся на 100 замовленнях
    2. Added — address.postcode з тим самим значенням, що й address.zip
    3. Deprecated — address.zip. Читайте натомість address.postcode. zip лишається до 3.0.0 і щонайменше до 15 березня 2027 року; перше читання в процесі пише попередження
    4. Security — email клієнта більше не потрапляє в debug-лог (DEBUG=orders-client). Оновіться негайно, якщо debug-логування хоч раз вмикали в production
  4. 4.[2.6.2] — 2026-08-27

    1. Fixed — order.placedAt знову в , як і задокументовано. 2.6.1 повертала локальний час сервера
  5. 5.[2.6.1] — 2026-08-26 [YANKED]

    1. Fixed — order.total тепер враховує знижку за ваучером. Не використовуйте цю версію: вона ще й перевела placedAt на локальний час, і 212 пізніх вечірніх замовлень потрапили в нічному вивантаженні не в той день. Відкликано наступного ранку; використовуйте 2.6.2
  6. 6.[2.0.0] — 2026-03-02

    1. Removed — getOrderLines(orderId). Рядки замовлення тепер в order.lines. Див. «Перехід на 2.0», крок 1
    2. Changed — гроші тепер ціле число найменших одиниць валюти: total: 4999, а не "49.99". Кожне місце, що форматує чи додає ціни, треба змінити. Див. «Перехід на 2.0», крок 2
  7. 7.Посилання

    1. Кожен заголовок версії веде на свій diff — [2.7.0]: …/compare/v2.6.2...v2.7.0, — а [Unreleased] порівнює останній тег із main

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 означає, що наявним користувачам треба щось змінити. 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.jsMAJORЗа домовленістю — специфікація Conventional Commits бере саме це за приклад
Змінити спосіб завантаження пакета — ES-модуль, мапа exportsMAJORІмпорти, що працювали, перестають розв’язуватися; див. is-promise нижче
Оновити залежність без зміни публічного APIPATCH або MINORFAQ: сумісно, а яке з двох — вирішує причина оновлення
Три питання, поставлені по черзі, вирішують, яке число зростає. Зміна поза оголошеним публічним API — вільна, щонайбільше patch. Усередині нього зміна, що змушує будь-кого з користувачів правити свій код, — це MAJOR, як рядки 6.1 і 6.2 changelog. Зміна, що щось додає чи депрекує, — MINOR, як 3.1 і 3.3. Усе інше — PATCH, як 4.1. Більшість підняттів вирішує друге питання, а відповісти на нього може лише знання того, як люди користуються програмою.

Нуль, пререлізи та метадані збірки

Усе, що починається з 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. тримає стару версію, доки її хтось не оновить, — це відкладає сюрприз, але не скасовує його. Тому MINOR, що щось ламає, розходиться далі й швидше, ніж будь-коли зміг би MAJOR.

Інші схеми нумерації — і що обіцяє кожна

СхемаПрикладЧисло кажеСумісність обіцяє
Semantic VersioningБібліотеки й пакети: 2.7.0Який це вид зміниСаме число
Календарне версіонуванняUbuntu 24.04 (рік і місяць); pip 25.2 (рік і реліз)Скільки йому роківОкрема політика — pip депрекує щонайменше на шість місяців
Датовані версії APIStripe: 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" веде на сторінку з поясненням переходу. 9745 додає два правила, які варто знати. Депрекація «не змінює жодної поведінки ресурсу», а дата Sunset не може бути ранішою за дату Deprecation.

Заголовок позначає ресурс, а не поле, тож рядок 3.3 він оголосити не може. Про address.zip повідомляють changelog і документація, а бібліотека пише попередження, коли процес уперше його читає. Саме з цього попередження команда й дізнається, хто досі від нього залежить.

Рядок 3.3 депрекує address.zip у версії 2.7.0, а рядок 1.3 ставить найраніше видалення через шість місяців — на 15 березня 2027 року. Чотири команди-споживачі переходять на postcode у власному темпі. До січня перейшли три; аналітика в березні досі читає zip, тож їй кажуть особисто, і 30 березня вона переходить. Версія 3.0.0 виходить 13 квітня, після переходу останнього читача, — а не в найранішу дату, яку дозволяла політика.

Як далеко чесно заходить автоматизація

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:PATCHFixed
feat:MINORAdded
! або BREAKING CHANGE:MAJORChanged або Removed
chore:, docs:, refactor:, test:НічогоЗазвичай нікуди — жодного ефекту, який побачить користувач
Deprecated, SecurityВласного типу немаєТуди їх має поставити людина, — коміт у порівнянні вище був звичайним fix

Це справді допомагає. Інструмент може вивести наступний номер версії й зібрати чернетку під правильними заголовками, а це два найнудніші кроки. Чого він не може — вирішити, чи є зміна ламкою, бо це залежить від того, як люди користуються продуктом, а це знання живе поза репозиторієм. Коміт із міткою fix:, що змінює значення за замовчуванням, є ламким, — а інструмент цілком упевнено назве його патчем.

ІнструментЧитаєСтворюєДе вирішує людина
semantic-releaseПовідомлення комітівВерсію, нотатки й опублікований пакет — без кроку людиниЛише в кожному повідомленні коміту; README каже, що він «прибирає безпосередній зв’язок між людськими емоціями й номерами версій»
release-pleaseConventional CommitsРеліз-pull request, що тримає changelog і підняття версії актуальнимиРедагування й злиття цього pull request
ChangesetsНевеликий Markdown-файл на зміну: підняття й короткий описChangelog і нові версіїУ pull request, автором, на рев’ю разом із кодом
towncrier«Фрагменти новин» — файл на зміну, написаний для кінцевих користувачівФайл новин на релізі; ним користуються pip, pytest і TwistedУ pull request, як у Changesets
З повідомлень комітів рядок changelog пишуть на релізі по пам’яті, а підняття вгадують за типами комітів, — тож fix, що змінив значення за замовчуванням, виходить патчем. З фрагментами кожен pull request несе власний рядок і підняття, написані автором і переглянуті разом із кодом, тож рецензент бачить зміну значення за замовчуванням і рядок, що її описує, разом. Потім реліз-pull request збирає фрагменти.

Останні два відповідають на проблеми, які порушили попередні розділи. Запис пише той, хто розуміє зміну, поки вона свіжа, і його переглядають у тому самому , що й код, — тож рецензент, який бачить зміну значення за замовчуванням, бачить і рядок, що називає її патчем. А оскільки кожна зміна приносить власний файл, немає єдиного CHANGELOG.md, який редагує кожна гілка і на якому конфліктує кожне злиття. README towncrier пояснює причину прямо: він доносить новини так, як «зручно тим, хто їх чує, а не тим, хто їх пише».

Чотири релізи, де проблемою був рядок changelog

Кожен кейс нижче задокументував сам проєкт. У жодному з них код не був усією історією. Щоразу не дійшло до читача число, рядок чи сигнал, які мали б дійти.

КейсЩо сталосяЧого потребував читачРядки
is-promise, 25 квітня 2020Minor-реліз змінив спосіб завантаження пакета; популярні інструменти ламалися на свіжих встановленнях три годиниЧисло MAJOR1.2, 6.2
Kubernetes 1.16, 18 вересня 2019API, задепрековані майже два роки, перестали відповідати; багато хто вперше про це почув саме тодіСигнал там, де працює код, а не лише рядок у реліз-нотах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 під тиском

-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 р.

Дослідження з Claude. Пошук і звірку першоджерел зроблено з Claude (Anthropic). Відбір, текст і перевірку фактів — редакція Anatomy of IT Product.

Було корисно?

Поділіться з тим, хто працює над тією ж задачею.