Semantic Versioning 2.0.0

本文章翻譯自 https://semver.org/
中文版在這里 https://semver.org/lang/zh-CN/,但讀起來太困難。這里搞一版更教程化, 但又不失嚴(yán)謹(jǐn)?shù)摹?/p>

一個良好的版本號的結(jié)構(gòu)與改動規(guī)則,向用戶傳達(dá)了我們軟件中改動的影響級別。
作者在這篇官方文檔里,給出了對 semver 的精準(zhǔn)定義。

搬運(yùn)正文

概述

給定一個版本號: MAJOR.MINOR.PATCH (主版本號.次版本號.補(bǔ)丁版本號):

  1. 當(dāng)你修改了 API,使其(與之前版本)不兼容時,遞增 MAJOR,
  2. 當(dāng)你用向后兼容的方式加了些功能時,遞增 MINOR,
  3. 當(dāng)你用向后兼容的方式解決了幾個 Bug 時,遞增 PATCH。

預(yù)發(fā)布版本 附加的標(biāo)簽,以及與編譯相關(guān)的額外信息,可以作為 MAJOR.MINOR.PATCH 這種格式的擴(kuò)展,加到版本號的后面。

引言

在軟件管理的世界里, 有個可怕的地方, 叫 "Dependency Hell"[1]. 你的系統(tǒng)規(guī)模增長的越大, 集成到系統(tǒng)里的軟件包越多, 你就越有可能發(fā)現(xiàn), 某天, 你已經(jīng)深深的陷入了這種絕望之地.

在那些有很多依賴包的系統(tǒng)里, 發(fā)布新的軟件包版本很快就會變成一個夢靨. 如果依賴要求太緊, 你可能會陷入 "版本鎖定" [2]. 如果版本要求太松, 你又不可避免的遭受"版本濫交"[3]之痛. [4]. 而所謂的 "Dependency Hell", 就是當(dāng) "版本鎖定" 和/或 "版本濫交" 阻止你簡單, 安全的推動項(xiàng)目前行的時候, 你的處境.

作為這個問題的一個解決方案, 我提議一套簡單的規(guī)則和要求, 以規(guī)定如何分配和增長版本號. 這些規(guī)定基于, 但不限于已經(jīng)在各種閉源, 開源軟件中廣泛使用的普遍慣例. 要想這套理論奏效, 首先你得聲明一個公開的 API. API 可能是由文檔組成的, 也可能是直接使用代碼實(shí)現(xiàn)的. 但不管怎樣, 重要的是這個 API 是清晰和精確的. 一旦你確定了你的 API, 使用增加特定的版本號的方式, 來傳達(dá) API 的改動. 考慮一個 X.Y.Z (MAJOR.MINOR.PATCH) 的版本號格式: 那么, 不影響 API 的Bug修復(fù): 遞增補(bǔ)丁版本號Z; 向后兼容的 API 的添加或修改: 遞增次版本號; 不向后兼容的 API 的修改: 遞增主版本號.

這套理論我稱作 "Semantic Versioning", 那些個版本號以及他們的改變傳達(dá)著與 底層代碼, 以及從一個版本到另一個版本改了什么 相關(guān)的含義.

Semantic Versioning 規(guī)范

  1. 使用 Semantic Versioning 的軟件 必須 聲明一個公共的 API. 這個 API 可能是定義在代碼里的, 或者僅僅存在于文檔里, 不論用什么方式實(shí)現(xiàn), 它都必須精確而全面.

  2. 一個正常的版本號必須使用 X.Y.Z 的格式, 其中 X, Y, 和 Z 都是非負(fù)的整數(shù), 并且 必須不能 包含前導(dǎo)零.
    X 是主版本號, Y 是次版本號, 而 Z 是補(bǔ)丁版本號. 每個元素都必須以數(shù)字的方式遞增. 舉例: 1.9.0 -> 1.10.0 -> 1.11.0.

  3. 一旦一個打了版本的包被發(fā)布出去了, 那個版本的內(nèi)容就 不能 再修改了. 任何修改 必須 作為一個新的版本重新發(fā)布.

  4. 主版本為零 (0.y.z) 的版本, 是用作初始開發(fā)階段的. 任何東西都可能在任意的時間被更改. 這時候我們不應(yīng)該認(rèn)為它的 API 是穩(wěn)定的.

  5. 1.0.0 版本表明對外公開 API 的形成. 從此之后, 版本號的遞增方式取決于這個公開的API, 以及它如何修訂.

  6. 補(bǔ)丁版本號Z (x.y.Z | x > 0) . 如果只有向后兼容的bug修復(fù)被引入的化, 補(bǔ)丁版本號 Z 必須 遞增. "Bug修復(fù)"是指一個修正錯誤行為的內(nèi)部修改.

  7. 次版本號Y (x.Y.z | x > 0). 如果一個新的, 向后兼容的功能被引入到了公開 API 里, 次版本號 必須 遞增. 如果公開 API 的任何功能被標(biāo)記為 "已棄用的", 次版本號 必須 遞增. 如果大量的新功能或改進(jìn)被引入到私有代碼里的時候, 次版本號 可以 遞增. 次版本號的改變 可以 包含補(bǔ)丁級別的改動. 當(dāng)遞增了次版本號的時候, 補(bǔ)丁版本號 必須 清零.

  8. 主版本號X (X.y.z | X > 0). 如果任何的向后不兼容的改動被引入到了公開 API中, 主版本號 必須 遞增. 它的遞增 可以 包含次版本和補(bǔ)丁級的改動. 當(dāng)主版本號遞增時, 次版本號和補(bǔ)丁版本號 必須 清零.

  9. 一個預(yù)發(fā)布版本 可以 通過在補(bǔ)丁版本號后面追加一個短線, 以及一系列的用點(diǎn)分割的標(biāo)識符 來描述. 標(biāo)識符 必須 僅包含 ASCII 的 阿拉伯?dāng)?shù)字和短線 [0-9A-Za-z-]. 標(biāo)識符 必須不 為空. 數(shù)字標(biāo)識符 不能 包含前導(dǎo)零. 預(yù)發(fā)布版本比對應(yīng)的正常版本的優(yōu)先級要低. 預(yù)發(fā)布版本表明, 它不穩(wěn)定, 并且可能不滿足其對應(yīng)的正常版本所預(yù)定的兼容性要求. 例子: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92.

  10. 編譯時的附加信息, 可以 通過在補(bǔ)丁版本號后面追加一個加號, 以及一系列的用點(diǎn)分割的標(biāo)識符 來描述. 標(biāo)識符 必須 僅包含 ASCII 的 阿拉伯?dāng)?shù)字和短線 [0-9A-Za-z-]. 標(biāo)識符 必須不 為空. 在比較版本優(yōu)先級的時候, 編譯附加信息 應(yīng)該 被忽略. 因此, 兩個只有編譯附加信息不同的版本, 具有相同的優(yōu)先級. 編譯附加信息的舉例: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85.

  11. 優(yōu)先級是指在排序的時候怎樣比較不同的版本. 計(jì)算優(yōu)先級的時候, 必須 將版本號以 "主版本號", "次版本號", "補(bǔ)丁版本號", "預(yù)發(fā)布標(biāo)識符" 的順序拆分. 優(yōu)先級取決于, 在從左至右依次比較這些個標(biāo)識符的時候, 發(fā)現(xiàn)的第一個差別. "主版本號", "次版本號", "補(bǔ)丁版本號" 總是以數(shù)字的方式參加比較. 舉例: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1.
    當(dāng)"主版本號", "次版本號", "補(bǔ)丁版本號" 都相同的時候, 預(yù)發(fā)布版本比正常的版本優(yōu)先級要低. 舉例: 1.0.0-alpha < 1.0.0.
    如果兩個預(yù)發(fā)布版本有相同的 "主版本號", "次版本號", "補(bǔ)丁版本號", 優(yōu)先級就 必須 通過比較點(diǎn)分割的標(biāo)識符來確定, 從左至右依次比較, 直到發(fā)現(xiàn)一個不同: 只有數(shù)字的標(biāo)識符號以數(shù)值高低比較, 有字母或連接號時則逐字以 ASCII 的排序來比較. 數(shù)字的標(biāo)識符號比非數(shù)字的標(biāo)識符號優(yōu)先級低. 若開頭的標(biāo)識符號都相同時, 字段比較多的預(yù)發(fā)布版本號優(yōu)先層級高. 舉例: 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.

為什么使用 Semantic Versioning ?

(我的這套理論) 并不是什么新的或者革命性的點(diǎn)子,實(shí)際上,很可能你已經(jīng)做了些很接近這個的工作。但問題在于,僅僅是“接近”并不夠。如果不遵守有點(diǎn)兒"官方"的協(xié)定,版本號對于管理依賴來說基本沒什么用。通過給我上面的思想命名,并給予其清晰的定義,將你的意圖傳達(dá)給你的軟件的用戶就變得很簡單了。一旦意圖清晰明了,最終一個靈活的(但也不是過于靈活的)軟件依賴要求就可以搞定了。
我們可以通過一個簡單的例子,展示一下 Semantic Versioning 可以讓 “Dependency Hell” 成為歷史??紤]一個叫做 “消防車” 的版本庫,他需要一個使用了 Semantically Version 的軟件包 “梯子”。當(dāng) “消防車” 剛創(chuàng)建的時候,“梯子”在3.1.0 版本。因?yàn)椤跋儡嚒笔褂昧?“梯子” 3.1.0 新引入的功能,你可以簡單的指定,你的“消防車” 對“梯子”的依賴要求是:大于等于 3.1.0 ,但小于 4.0.0 ?,F(xiàn)在,梯子 "3.1.1" 和 "3.2.0" 好了,你可以把他們發(fā)布到你的軟件包管理系統(tǒng),并且你知道他們跟現(xiàn)有的、跟它有依賴關(guān)系的包是兼容的。
作為一個負(fù)責(zé)人的開發(fā)者,你當(dāng)然想驗(yàn)證任何軟件包升級都是正常的、跟你宣傳的一樣。但現(xiàn)實(shí)世界是一團(tuán)亂麻,對此我們除了小心再小心之外沒有更好的辦法。你能做的,是讓 “Semantic Versioning” 提供你一個合理的方法去發(fā)布、升級軟件包,而不必去搞許多新版的依賴包,節(jié)省了你的時間,免去了許多麻煩。
如果所有這些聽起來挺給力的,要開始使用 “Semantic Versioning” 的話,你只需要聲明你正在這么搞,然后遵守這些規(guī)范就好了。把這個網(wǎng)站鏈接到你的 README 文檔里,以便其他其他人也可以了解這些規(guī)則,并從中受益。

FAQ

  • 在初始開發(fā)階段,怎么去處理 0.y.z 的版本號?
    一個簡單的做法是, 使用 0.1.0 作為第一版初始開發(fā)版本號,然后為隨后的發(fā)布包遞增次版本號(minor version)
  • 我怎么知道什么時候發(fā)布 1.0.0 版?
    如果你的軟件已經(jīng)在生產(chǎn)環(huán)境了(已經(jīng)上線了), 很可能已經(jīng) 1.0.0 了。如果你做好了一個 從此用戶可以信賴的、穩(wěn)定的API版本,你應(yīng)該發(fā)布1.0.0。如果你正為向后兼容的事情心煩,你應(yīng)該早就 1.0.0 了。
  • 這東西難道不會阻撓快速開發(fā)、快速迭代嗎?
    為零的主版本就是為了快速開發(fā)的。如果你每天都在改 API,你要么還在 0.y.z,要么在另外一個開發(fā)分支上,為下一個主版本做準(zhǔn)備。
  • 如果,哪怕是微小的 API 的不兼容改動,主版本都要蹦,我豈不是很快就到 42.0.0 版了?
    這是個關(guān)于為開發(fā)負(fù)責(zé),以及前瞻性的問題。在有許多代碼依賴之的軟件中,不應(yīng)該輕率的做不兼容的改動。升級招致的代價可能是相當(dāng)大的。不得不通過遞增主版本號來發(fā)行不兼容的改版,意味著你將充分考慮改動所帶來的影響,并且評估所涉及的 成本/收益 比。
  • 整理個 API 的文檔太費(fèi)事兒了!
    為供他人使用的軟件編寫適當(dāng)?shù)奈募?,是你作為一名專業(yè)的開發(fā)者應(yīng)盡的職責(zé)?!肮芾眄?xiàng)目復(fù)雜度” 是保持項(xiàng)目高效的非常重要的一部分,而如果沒有人知道如何使用你的軟件,或者不知道哪些函數(shù)可以放心的調(diào)用的話,就不好做。Semantic Versioning,以及對 良好定義的API 的堅(jiān)持,可以讓每個人、每件事情都順利進(jìn)行。
  • 要是我不小心把一個不向后兼容的改動當(dāng)成一個次版本號發(fā)布了怎么辦?
    一旦發(fā)現(xiàn)你破壞了 Semantic Versioning 規(guī)范,馬上解決這個問題,然后發(fā)布一個新的次版本,以恢復(fù)向后兼容。即使在這種情況下,直接修改已經(jīng)發(fā)行的版本也是不可接受的。如合適,在文檔里寫明這個有問題的版本,并將這個問題告知你的用戶,以便用戶知曉這個出問題的版本。
  • 如果我在沒有更新API的前提下,更新了我自己(軟件)的依賴,應(yīng)該怎么做?
    由于沒有影響到公共 API,這將被當(dāng)做是兼容的。那些使用了跟你一樣的依賴包的軟件,應(yīng)該也有自己的依賴要求,并且如果有沖突的話,他們的作者會注意到的。要判定改動是屬于補(bǔ)丁級別還是次版級別,要看你更新依賴包是為了修復(fù)Bug,還是添加新功能。對于后者,我通常覺著會有額外的代碼,這種情況下,顯然是一個次版本號級別的遞增。
  • 如果我變更了公共 API 但無意中未遵循版本號的改動怎么辦呢?(意即在補(bǔ)丁級的發(fā)布中,誤將重大且不兼容的改變加到了代碼之中)
    自行做最佳的判斷。如果改回 API 預(yù)期的行為將強(qiáng)烈的影響你的大量受眾,那么可能最好再發(fā)一個主版本吧,即使這個修復(fù)僅僅被當(dāng)做一個補(bǔ)丁版本。記住,Semantic Versioning 所做的就是,通過版本號的改動傳達(dá)含義。若這些改變對你的使用者很重要,那就通過版本號來告知他們。
  • 我該如何處理即將棄用的功能?
    棄用現(xiàn)存的功能,是軟件開發(fā)中正常的一部分,也通常是向前發(fā)展所必須的。當(dāng)你棄用部份 API 時,你應(yīng)該做兩件事:(1)更新你的文檔讓使用者知道這個改變(2)發(fā)布一個新的、仍然包含這些已經(jīng)棄用的API 的次版本。在你從新的主版本里完全移除這些已棄用的功能之前,至少要有一個次版本 仍然包含這些已經(jīng)棄用的 API,這樣使用者才能平滑地轉(zhuǎn)移到新版 API。
  • Semantic Versioning 對于版本的字串長度是否有限制呢?
    沒有,但自行判斷。舉例來說,一個包含255個字符的版本字符串很可能太過分了。并且,特定的系統(tǒng)對于字串長度可能會有他們自己的限制。

  1. "依賴地獄". 因?yàn)椴缓梅g, 就不譯了. ?

  2. "version lock" (如果不為每一個依賴包都發(fā)布一個新版本, 就沒辦法升級某個軟件包). ?

  3. "version promiscuity" (承擔(dān)了與太多未來版本兼容的責(zé)任, 遠(yuǎn)遠(yuǎn)超過合理的需要). ?

  4. 依賴過緊舉例: 假設(shè)軟件 A 依賴軟件 B, 聲明 A 的當(dāng)前版需要 B 的 v1.1 版本, A 的下一個版本需要 B 的 v1.2 版本. 這就過緊了. 這樣如果要將A升級到下一個版本, 你就不得不同時發(fā)布 B 的 v1.2 版本; 依賴過松舉例: 聲明 A的當(dāng)前版本需要B, 只要 B 的版本大于 v1.1 即可. 這樣子A 負(fù)擔(dān)過重了, 處理與太多的B的未來版本的兼容問題, 沒什么必要. ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容