Семантичне Версіонування 2.0.0

Коротко

У випадку, коли версія має вигляд МАЖОРНА.МІНОРНА.ПАТЧ, слід збільшувати:

  1. МАЖОРНУ версію, якщо зроблені зміни API, що несумісні з попередньою версією
  2. МІНОРНУ версію, якщо додана нова функціональність, що є сумісною з попередньою версією
  3. ПАТЧ версію, якщо були зроблені виправлення помилок, що не впливають на сумісність з попередньою версією

Додаткові позначки для передрелізних збірок дозволені, як розширення до формату МАЖОРНА.МІНОРНА.ПАТЧ.

Вступ

У світі управління програмним забезпеченням існує таке поняття, як “dependency hell” (пекло залежностей). Із розростанням системи та інтеграцією в неї великої кількості пакетів дуже ймовірно опинитись у цій ситуації.

У системах з багатьма залежностями випуск нових версій пакетів може швидко перетворитись на жах. Якщо специфікації залежностей занадто жорсткі, існує небезпека блокування випуску нової версії (неможливість оновити пакет без випуску нових версій кожного залежного пакета). Якщо ж залежності специфіковані занадто вільно, ви неминуче будете покарані безладом у версіях (припускаючи сумісність з більшою кількістю майбутніх версій, ніж це доцільно). Пекло залежностей ― це ситуація, коли блокування версій та/або несумісність версій заважає легко і безпечно просувати ваш проект вперед.

В якості вирішення цієї проблеми пропонується простий набір правил і вимог, які визначають те, як призначаються та збільшуються номери версій. Ці правила ґрунтуються (але цим не обмежуються) на існуючих поширених практиках, що використовуються як в закритому, так і у відкритому програмному забезпеченні. Щоб ця система працювала, спочатку потрібно оголосити публічний API. Він може описуватись в документації, або ж безпосередньо кодом. Незалежно від форми, важливо, щоб цей API був чітким і точним. Після того, як був визначений публічний API, ви сповіщаєте про його зміни шляхом збільшення певних номерів версії. Розглянемо формат версії X.Y.Z (МАЖОРНА.МІНОРНА.ПАТЧ). Виправлення помилок, які не впливають на API, збільшують ПАТЧ-версію. Розширення/зміни API, що сумісні з попередньою версією, збільшують МІНОРНУ версію. Ті ж зміни API, що несумісні із минулою версією, збільшують МАЖОРНУ версію.

Цю систему названо “Семантичне Версіонування”. Відповідно до неї, номери версій і спосіб їх зміни передають інформацію про базовий код і про те, що змінено від попередньої до нової версії.

Специфікація Семантичного Версіонування (SemVer)

Ключові слова “ПОВИНЕН” (MUST), “НЕ ПОВИНЕН” (MUST NOT), “ОБОВ’ЯЗКОВО” (REQUIRED), “МАЄ” (SHALL), “НЕ МАЄ” (SHALL NOT), “БАЖАНО” (SHOULD), “НЕ БАЖАНО” (SHOULD NOT), “РЕКОМЕНДОВАНО” (RECOMMENDED), “МОЖЕ” (MAY), та “НЕ ОБОВ’ЯЗКОВО” (OPTIONAL), що використані в цьому документі, повинні бути інтерпретовані за RFC 2119.

  1. Програмне забезпечення, що використовує Семантичне Версіонування, ПОВИННЕ оголосити публічний API. Цей API може бути оголошений безпосередньо в коді, або ж існувати лише у вигляді документації. Незалежно від типу оголошення, воно повинне бути точним і всебічним.

  2. Правильний номер версії ПОВИНЕН мати форму X.Y.Z, де X, Y і Z є невід’ємними цілими числами, і НЕ ПОВИННІ мати нулі на початку. X ― мажорна версія, Y ― мінорна версія, а Z ― патч версія. Кожен елемент повинен збільшуватися чисельно. Наприклад: 1.9.0 -> 1.10.0 -> 1.11.0.

  3. Після випуску пакета конкретної версії, він НЕ ПОВИНЕН змінюватись. Будь-які зміни ПОВИННІ бути випущені, як нова версія.

  4. Нульова мажорна версія (0.y.z) призначена для початкової розробки. Будь-що МОЖЕ змінюватись в будь-який час. Публічний API такої версії не слід вважати стабільним.

  5. Версія 1.0.0 визначає публічний API. Спосіб, яким збільшуються номери версій після цього випуску, залежить від цього публічного API і від того, як він змінюється.

  6. Патч версія Z (x.y.Z | x > 0) ПОВИННА бути збільшена тільки якщо вона містить лише зворотньосумісні виправлення помилок. Виправленою помилкою називається внутрішня зміна, яка виправляє неправильну поведінку.

  7. Мінорна версія Y (x.Y.z | x > 0) ПОВИННА бути збільшена, якщо до публічного API додана нова зворотньосумісна функціональність. Вона ПОВИННА бути збільшена, якщо будь-яка функціональність публічного API позначена, як застаріла (deprecated). Вона МОЖЕ бути збільшена, якщо в приватний код внесені істотні зміни функціональних можливостей або вдосконалення. Вона МОЖЕ включати зміни рівня патчів. Патч версія ПОВИННА бути скинута до 0 при збільшенні мінорної версії.

  8. Мажорна версія X (X.y.z | X > 0) ПОВИННА бути збільшена, якщо до публічного API внесені будь-які зміни, що не сумісні з попередньою версією. Вона може включати зміни рівня мінорної та патч версій. Номери патч версії та мінорної версії ПОВИННІ бути скинуті до 0 при збільшенні мажорної версії.

  9. Передрелізна версія МОЖЕ бути позначена шляхом додавання безпосередньо після патч версії дефісу і ряду ідентифікаторів, розділених крапками. Ідентифікатори ПОВИННІ містити лише алфавітно-цифрові символи 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.

  10. Метадані збірки МОЖНА позначати додаванням знаку плюс і ряду ідентифікаторів, розділених крапками, відразу після номеру патч версії або передрелізної версії. Ідентифікатори ПОВИННІ містити лише алфавітно-цифрові символи ASCII та дефіс [0-9A-Za-z-]. Ідентифікатори НЕ ПОВИННІ бути порожніми. Метадані збірки ПОВИННІ ігноруватися при визначенні пріоритету версії. Таким чином, дві версії, які відрізняються тільки метаданими, мають однаковий пріоритет. Приклади: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85.

  11. Пріоритет визначає, як версії порівнюються одна з одною при упорядкуванні. Пріоритет ПОВИНЕН визначатись шляхом поділу версії на мажорний, мінорний, патч та передрелізний ідентифікатори саме в такому порядку (метадані збірки не впливають на пріоритет). Пріоритет визначається першою відмінністю під час порівняння кожного з цих ідентифікаторів зліва направо наступним чином: мажорні, мінорні та патч версії завжди порівнюються чисельно. Приклад: 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.

Навіщо використовувати Семантичне Версіонування?

Це не нова або революційна ідея. Насправді, вже існує багато близького до того. Проблема в тому, що “близького до того” недостатньо. Без відповідності певній формальній специфікації, номери версій є майже непридатними для управління залежностями. Надавши назви та чіткі визначення вищенаведеним ідеям, стає легко повідомляти про свої наміри кінцевим користувачам програмного забезпечення. Після того, як ці наміри стануть зрозумілими, нарешті можуть бути зроблені гнучкі (але не занадто гнучкі) специфікації для ведення залежностей.

Простий приклад продемонструє, як Семантичне Версіонування може залишити “dependency hell” в минулому. Розглянемо бібліотеку під назвою “Firetruck”. Вона залежить від Семантично Версіонованого пакету під назвою “Ladder”. На момент створення Firetruck, Ladder мав версію 3.1.0. Оскільки Firetruck використовує деяку функціональність Ladder, яка вперше була введена у версії 3.1.0, можна сміливо вказати на залежність від версій пакету Ladder, які більші або дорівнюють 3.1.0, але менші за 4.0.0. Тепер, коли версії 3.1.1 та 3.2.0 пакету Ladder стають доступними, їх можна буде опублікувати за допомогою системи управління пакетами і бути певним, що вони будуть сумісні з існуючим залежним від нього програмним забезпеченням.

Відповідальний розробник, звичайно, бажає переконатися, що будь-які оновлення пакета функціонують згідно з документацією. Реальний світ ― це хаотичне місце і ми нічого не можемо зробити, окрім, як бути пильними. Що можна зробити, це використовувати Семантичне Версіонування для випуску та оновлення пакетів без необхідності оновлення залежностей, заощаджуючи час і нерви.

Якщо це звучить цікаво, то все що потрібно зробити ― це почати користуватися Семантичним Версіонуванням, заявити про це, і дотримуватись правил. Додайте посилання на цей веб-сайт у README до проекту, щоб інші мали змогу дізнатись правила та скористатися ними.

FAQ

Як працювати з релізами у початковій фазі розробки 0.y.z?

Найпростіше зробити початковий реліз на рівні 0.1.0, а потім збільшувати мінорну версію для кожного наступного випуску.

Як дізнатися, коли потрібно випустити 1.0.0?

Якщо програмне забезпечення вже знаходиться експлуатації, то вже має бути 1.0.0. Якщо існує стабільний API, від якого залежать користувачі, повинна бути версія 1.0.0. Якщо вже починаються турботи про зворотну сумісність, має бути 1.0.0.

Чи не перешкоджає це стрімкій розробці та швидким ітераціям?

Нульова мажорна версія ― це активна стадія розробки. Якщо API змінюється щодня, треба або залишатись у версії 0.y.z або на окремій гілці розробки, працюючи над наступною мажорною версією.

Якщо навіть найдрібніші зміни, що не сумісні з попередньою версією до публічного API, вимагають збільшення мажорної версії, чи не призведе це до версії 42.0.0 занадто швидко?

Це питання відповідального розвитку і передбачення. Не слід легковажно ставитись до публікації несумісних змін програмного забезпечення, яке має багато залежного від нього коду. Витрати на модернізацію можуть бути дуже значними. Реліз мажорної версії з несумісними змінами означає, що вплив змін детально обдуманий, а співвідношення витрати/користь ― оцінене.

Повне документування публічного API ― це забагато роботи!

Професійний розробник має нести відповідальність за належне документування програмного забезпечення, призначеного для використання іншими користувачами. Управління складністю проекту є надзвичайно важливим фактором його ефективності, і цього важко досяги, якщо ніхто не розуміє, як це програмне забезпечення використовувати, або які методи безпечні для виклику. У довгостроковій перспективі, Семантичне Версіонування та наполягання на чітко визначеному публічному API дозволить всім і всьому працювати безперешкодно.

Що робити, якщо у якості мінорної версії випадково була випущена зміна, що несумісна з попередньою версією?

Як тільки стало відомим, що порушена Специфікація Семантичного Версіонування, треба виправити проблему і випустити нову мінорну версію, яка виправляє проблему і відновлює зворотню сумісність. Та навіть за таких обставин неприпустимо вносити зміни до вже випущених версій коду. Якщо це доречно, є сенс задокументувати версію, що порушує правила, та поінформувати користувачів про проблему, щоб вони знали про версію, яка порушує правила.

Що робити при оновленні внутрішніх залежностей, якщо публічний API не змінений?

Такі зміни вважаються сумісними, оскільки не впливають на публічний API. Програмне забезпечення, яке явно залежить від тих самих залежностей, повинне мати власні специфікації залежностей, і автор помітить будь-які конфлікти. Визначення того, чи є така зміна модифікацією рівня патча або рівня мінорної версії, залежить від того, чи внутрішні залежності були оновлені з метою виправлення помилки або з метою введення нової функціональності. В останньому випадку зазвичай слід очікувати додавання деякої кількості коду, що, вочевидь, є зміною рівня мінорної версії.

Що робити, якщо випадково був змінений публічний API у спосіб, що не відповідає зміненій версії (тобто, код має несумісні зміни у патч-релізі)?

На ваш розсуд. Якщо у вас є величезна аудиторія, на яку буде сильно впливати зміна публічного API, то краще всього зробити реліз мажорної версії, навіть якщо фактичне виправлення всього лише рівня патч-версії. Пам’ятайте, що за Семантичним Версіонуванням зміна версії повинна давати розуміння значущості змін. Якщо ці зміни важливі для користувачів, використовуйте номер версії, щоб повідомити їх.

Як оголосити застарілою (deprecated) деяку функціональність?

Оголошення застарілими існуючих функціональних можливостей є звичайною частиною розробки програмного забезпечення і часто є необхідною умовою прогресу. Коли потрібно оголосити застарілим частину публічного API, слід виконати дві речі: (1) оновити документацію, щоб користувачі могли дізнатися про зміну, (2) випустити мінорний реліз, що містить застарілу функціональність. Перш ніж повністю видалити застарілу функціональність у новому мажорному релізі, має бути принаймні один мінорний реліз, який містить функції, що будуть видалені. Таким чином, користувачі зможуть плавно перейти до нового API.

Чи має semver обмеження на розмір рядка версії?

Ні, але будьте розсудливими. Номер версії з 255 символів ― це, можливо, забагато. До того ж, певні системи можуть накладати свої власні обмеження на розмір рядка.

Про проект

Автором Специфікації Семантичного Версіонування є Том Престон-Вернер, засновник Gravatars та співзасновник GitHub.

Якщо ви бажаєте залишити відгук, відкрийте issue на GitHub.

Ліцензія

Creative Commons ― CC BY 3.0