本文章翻譯自 https://semver.org/
中文版在這里 https://semver.org/lang/zh-CN/豆胸,但讀起來太困難。這里搞一版更教程化, 但又不失嚴謹?shù)摹?/p>
一個良好的版本號的結(jié)構(gòu)與改動規(guī)則,向用戶傳達了我們軟件中改動的影響級別踱稍。
作者在這篇官方文檔里鼠冕,給出了對 semver 的精準定義。
搬運正文
概述
給定一個版本號: MAJOR.MINOR.PATCH (主版本號.次版本號.補丁版本號):
- 當你修改了 API,使其(與之前版本)不兼容時溅漾,遞增 MAJOR,
- 當你用向后兼容的方式加了些功能時山叮,遞增 MINOR,
- 當你用向后兼容的方式解決了幾個 Bug 時,遞增 PATCH添履。
給 預發(fā)布版本 附加的標簽屁倔,以及與編譯相關的額外信息,可以作為 MAJOR.MINOR.PATCH 這種格式的擴展暮胧,加到版本號的后面锐借。
引言
在軟件管理的世界里, 有個可怕的地方, 叫 "Dependency Hell"[1]. 你的系統(tǒng)規(guī)模增長的越大, 集成到系統(tǒng)里的軟件包越多, 你就越有可能發(fā)現(xiàn), 某天, 你已經(jīng)深深的陷入了這種絕望之地.
在那些有很多依賴包的系統(tǒng)里, 發(fā)布新的軟件包版本很快就會變成一個夢靨. 如果依賴要求太緊, 你可能會陷入 "版本鎖定" [2]. 如果版本要求太松, 你又不可避免的遭受"版本濫交"[3]之痛. [4]. 而所謂的 "Dependency Hell", 就是當 "版本鎖定" 和/或 "版本濫交" 阻止你簡單, 安全的推動項目前行的時候, 你的處境.
作為這個問題的一個解決方案, 我提議一套簡單的規(guī)則和要求, 以規(guī)定如何分配和增長版本號. 這些規(guī)定基于, 但不限于已經(jīng)在各種閉源, 開源軟件中廣泛使用的普遍慣例. 要想這套理論奏效, 首先你得聲明一個公開的 API. API 可能是由文檔組成的, 也可能是直接使用代碼實現(xiàn)的. 但不管怎樣, 重要的是這個 API 是清晰和精確的. 一旦你確定了你的 API, 使用增加特定的版本號的方式, 來傳達 API 的改動. 考慮一個 X.Y.Z (MAJOR.MINOR.PATCH) 的版本號格式: 那么, 不影響 API 的Bug修復: 遞增補丁版本號Z; 向后兼容的 API 的添加或修改: 遞增次版本號; 不向后兼容的 API 的修改: 遞增主版本號.
這套理論我稱作 "Semantic Versioning", 那些個版本號以及他們的改變傳達著與 底層代碼, 以及從一個版本到另一個版本改了什么 相關的含義.
Semantic Versioning 規(guī)范
使用 Semantic Versioning 的軟件 必須 聲明一個公共的 API. 這個 API 可能是定義在代碼里的, 或者僅僅存在于文檔里, 不論用什么方式實現(xiàn), 它都必須精確而全面.
一個正常的版本號必須使用 X.Y.Z 的格式, 其中 X, Y, 和 Z 都是非負的整數(shù), 并且 必須不能 包含前導零.
X 是主版本號, Y 是次版本號, 而 Z 是補丁版本號. 每個元素都必須以數(shù)字的方式遞增. 舉例: 1.9.0 -> 1.10.0 -> 1.11.0.一旦一個打了版本的包被發(fā)布出去了, 那個版本的內(nèi)容就 不能 再修改了. 任何修改 必須 作為一個新的版本重新發(fā)布.
主版本為零 (0.y.z) 的版本, 是用作初始開發(fā)階段的. 任何東西都可能在任意的時間被更改. 這時候我們不應該認為它的 API 是穩(wěn)定的.
1.0.0 版本表明對外公開 API 的形成. 從此之后, 版本號的遞增方式取決于這個公開的API, 以及它如何修訂.
補丁版本號Z (x.y.Z | x > 0) . 如果只有向后兼容的bug修復被引入的化, 補丁版本號 Z 必須 遞增. "Bug修復"是指一個修正錯誤行為的內(nèi)部修改.
次版本號Y (x.Y.z | x > 0). 如果一個新的, 向后兼容的功能被引入到了公開 API 里, 次版本號 必須 遞增. 如果公開 API 的任何功能被標記為 "已棄用的", 次版本號 必須 遞增. 如果大量的新功能或改進被引入到私有代碼里的時候, 次版本號 可以 遞增. 次版本號的改變 可以 包含補丁級別的改動. 當遞增了次版本號的時候, 補丁版本號 必須 清零.
主版本號X (X.y.z | X > 0). 如果任何的向后不兼容的改動被引入到了公開 API中, 主版本號 必須 遞增. 它的遞增 可以 包含次版本和補丁級的改動. 當主版本號遞增時, 次版本號和補丁版本號 必須 清零.
一個預發(fā)布版本 可以 通過在補丁版本號后面追加一個短線, 以及一系列的用點分割的標識符 來描述. 標識符 必須 僅包含 ASCII 的 阿拉伯數(shù)字和短線 [0-9A-Za-z-]. 標識符 必須不 為空. 數(shù)字標識符 不能 包含前導零. 預發(fā)布版本比對應的正常版本的優(yōu)先級要低. 預發(fā)布版本表明, 它不穩(wěn)定, 并且可能不滿足其對應的正常版本所預定的兼容性要求. 例子: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92.
編譯時的附加信息, 可以 通過在補丁版本號后面追加一個加號, 以及一系列的用點分割的標識符 來描述. 標識符 必須 僅包含 ASCII 的 阿拉伯數(shù)字和短線 [0-9A-Za-z-]. 標識符 必須不 為空. 在比較版本優(yōu)先級的時候, 編譯附加信息 應該 被忽略. 因此, 兩個只有編譯附加信息不同的版本, 具有相同的優(yōu)先級. 編譯附加信息的舉例: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85.
優(yōu)先級是指在排序的時候怎樣比較不同的版本. 計算優(yōu)先級的時候, 必須 將版本號以 "主版本號", "次版本號", "補丁版本號", "預發(fā)布標識符" 的順序拆分. 優(yōu)先級取決于, 在從左至右依次比較這些個標識符的時候, 發(fā)現(xiàn)的第一個差別. "主版本號", "次版本號", "補丁版本號" 總是以數(shù)字的方式參加比較. 舉例: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1.
當"主版本號", "次版本號", "補丁版本號" 都相同的時候, 預發(fā)布版本比正常的版本優(yōu)先級要低. 舉例: 1.0.0-alpha < 1.0.0.
如果兩個預發(fā)布版本有相同的 "主版本號", "次版本號", "補丁版本號", 優(yōu)先級就 必須 通過比較點分割的標識符來確定, 從左至右依次比較, 直到發(fā)現(xiàn)一個不同: 只有數(shù)字的標識符號以數(shù)值高低比較, 有字母或連接號時則逐字以 ASCII 的排序來比較. 數(shù)字的標識符號比非數(shù)字的標識符號優(yōu)先級低. 若開頭的標識符號都相同時, 字段比較多的預發(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 ?
(我的這套理論) 并不是什么新的或者革命性的點子,實際上往衷,很可能你已經(jīng)做了些很接近這個的工作钞翔。但問題在于,僅僅是“接近”并不夠席舍。如果不遵守有點兒"官方"的協(xié)定布轿,版本號對于管理依賴來說基本沒什么用。通過給我上面的思想命名来颤,并給予其清晰的定義汰扭,將你的意圖傳達給你的軟件的用戶就變得很簡單了。一旦意圖清晰明了福铅,最終一個靈活的(但也不是過于靈活的)軟件依賴要求就可以搞定了萝毛。
我們可以通過一個簡單的例子,展示一下 Semantic Versioning 可以讓 “Dependency Hell” 成為歷史滑黔“拾考慮一個叫做 “消防車” 的版本庫,他需要一個使用了 Semantically Version 的軟件包 “梯子”略荡。當 “消防車” 剛創(chuàng)建的時候色查,“梯子”在3.1.0 版本。因為“消防車”使用了 “梯子” 3.1.0 新引入的功能撞芍,你可以簡單的指定秧了,你的“消防車” 對“梯子”的依賴要求是:大于等于 3.1.0 ,但小于 4.0.0 ⌒蛭蓿現(xiàn)在验毡,梯子 "3.1.1" 和 "3.2.0" 好了,你可以把他們發(fā)布到你的軟件包管理系統(tǒng)帝嗡,并且你知道他們跟現(xiàn)有的晶通、跟它有依賴關系的包是兼容的。
作為一個負責人的開發(fā)者哟玷,你當然想驗證任何軟件包升級都是正常的狮辽、跟你宣傳的一樣一也。但現(xià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版本诅岩,你應該發(fā)布1.0.0。如果你正為向后兼容的事情心煩带膜,你應該早就 1.0.0 了吩谦。- 這東西難道不會阻撓快速開發(fā)、快速迭代嗎膝藕?
為零的主版本就是為了快速開發(fā)的式廷。如果你每天都在改 API,你要么還在 0.y.z芭挽,要么在另外一個開發(fā)分支上滑废,為下一個主版本做準備。- 如果袜爪,哪怕是微小的 API 的不兼容改動蠕趁,主版本都要蹦,我豈不是很快就到 42.0.0 版了辛馆?
這是個關于為開發(fā)負責俺陋,以及前瞻性的問題。在有許多代碼依賴之的軟件中,不應該輕率的做不兼容的改動腊状。升級招致的代價可能是相當大的诱咏。不得不通過遞增主版本號來發(fā)行不兼容的改版,意味著你將充分考慮改動所帶來的影響寿酌,并且評估所涉及的 成本/收益 比胰苏。- 整理個 API 的文檔太費事兒了!
為供他人使用的軟件編寫適當?shù)奈募继郏悄阕鳛橐幻麑I(yè)的開發(fā)者應盡的職責硕并。“管理項目復雜度” 是保持項目高效的非常重要的一部分秧荆,而如果沒有人知道如何使用你的軟件倔毙,或者不知道哪些函數(shù)可以放心的調(diào)用的話,就不好做乙濒。Semantic Versioning陕赃,以及對 良好定義的API 的堅持,可以讓每個人颁股、每件事情都順利進行么库。- 要是我不小心把一個不向后兼容的改動當成一個次版本號發(fā)布了怎么辦?
一旦發(fā)現(xiàn)你破壞了 Semantic Versioning 規(guī)范甘有,馬上解決這個問題诉儒,然后發(fā)布一個新的次版本,以恢復向后兼容亏掀。即使在這種情況下忱反,直接修改已經(jīng)發(fā)行的版本也是不可接受的。如合適滤愕,在文檔里寫明這個有問題的版本温算,并將這個問題告知你的用戶,以便用戶知曉這個出問題的版本间影。- 如果我在沒有更新API的前提下注竿,更新了我自己(軟件)的依賴,應該怎么做魂贬?
由于沒有影響到公共 API蔓搞,這將被當做是兼容的。那些使用了跟你一樣的依賴包的軟件随橘,應該也有自己的依賴要求喂分,并且如果有沖突的話,他們的作者會注意到的机蔗。要判定改動是屬于補丁級別還是次版級別蒲祈,要看你更新依賴包是為了修復Bug甘萧,還是添加新功能。對于后者梆掸,我通常覺著會有額外的代碼扬卷,這種情況下,顯然是一個次版本號級別的遞增酸钦。- 如果我變更了公共 API 但無意中未遵循版本號的改動怎么辦呢怪得?(意即在補丁級的發(fā)布中,誤將重大且不兼容的改變加到了代碼之中)
自行做最佳的判斷卑硫。如果改回 API 預期的行為將強烈的影響你的大量受眾徒恋,那么可能最好再發(fā)一個主版本吧,即使這個修復僅僅被當做一個補丁版本欢伏。記住入挣,Semantic Versioning 所做的就是,通過版本號的改動傳達含義硝拧。若這些改變對你的使用者很重要径筏,那就通過版本號來告知他們。- 我該如何處理即將棄用的功能障陶?
棄用現(xiàn)存的功能滋恬,是軟件開發(fā)中正常的一部分,也通常是向前發(fā)展所必須的抱究。當你棄用部份 API 時恢氯,你應該做兩件事:(1)更新你的文檔讓使用者知道這個改變(2)發(fā)布一個新的、仍然包含這些已經(jīng)棄用的API 的次版本媳维。在你從新的主版本里完全移除這些已棄用的功能之前,至少要有一個次版本 仍然包含這些已經(jīng)棄用的 API遏暴,這樣使用者才能平滑地轉(zhuǎn)移到新版 API侄刽。- Semantic Versioning 對于版本的字串長度是否有限制呢?
沒有朋凉,但自行判斷州丹。舉例來說,一個包含255個字符的版本字符串很可能太過分了杂彭。并且墓毒,特定的系統(tǒng)對于字串長度可能會有他們自己的限制。
-
"依賴地獄". 因為不好翻譯, 就不譯了. ?
-
"version lock" (如果不為每一個依賴包都發(fā)布一個新版本, 就沒辦法升級某個軟件包). ?
-
"version promiscuity" (承擔了與太多未來版本兼容的責任, 遠遠超過合理的需要). ?
-
依賴過緊舉例: 假設軟件 A 依賴軟件 B, 聲明 A 的當前版需要 B 的 v1.1 版本, A 的下一個版本需要 B 的 v1.2 版本. 這就過緊了. 這樣如果要將A升級到下一個版本, 你就不得不同時發(fā)布 B 的 v1.2 版本; 依賴過松舉例: 聲明 A的當前版本需要B, 只要 B 的版本大于 v1.1 即可. 這樣子A 負擔過重了, 處理與太多的B的未來版本的兼容問題, 沒什么必要. ?