Семантичне Версіонування 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 використовує деякий функціонал, який був вперше введений в 3.1.0, можна сміливо вказати залежність Ladder, як більше або дорівнює 3.1.0, але менше 4.0.0 (3.1.0 <= version < 4.0.0). Тепер, коли Ladder версії 3.1.1 і 3.2.0 стають доступними, можна буде додати за допомогою системи управління пакунками і знати, що вони будуть сумісні з існуючим програмним забезпеченням.

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

Якщо це звучить цікаво, то все що потрібно зробити ― це почати користуватися Семантичним Версіонуванням, заявити про це, і дотримуватись правил. Додавши посилання на цей веб-сайт у 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