版本格式:主版本号.次版本号.修訂号,版本号遞增規則如下:
主版本号:當你做了不相容的 API 修改,
次版本号:當你做了向下相容的功能性新增,
修訂号:當你做了向下相容的問題修正。
先行版本号及版本編譯資訊可以加到“主版本号.次版本号.修訂号”的後面,作為延伸。
在軟體管理的領域裡存在着被稱作“依賴地獄”的死亡之谷,系統規模越大,加入的包越多,你就越有可能在未來的某一天發現自己已深陷絕望之中。
在依賴高的系統中釋出新版本包可能很快會成為噩夢。如果依賴關系過高,可能面臨版本控制被鎖死的風險(必須對每一個依賴包改版才能完成某次更新)。而如果依賴關系過于松散,又将無法避免版本的混亂(假設相容于未來的多個版本已超出了合理數量)。當你項目的進展因為版本依賴被鎖死或版本混亂變得不夠簡便和可靠,就意味着你正處于依賴地獄之中。
作為這個問題的解決方案之一,我提議用一組簡單的規則及條件來限制版本号的配置和增長。這些規則是根據(但不局限于)已經被各種封閉、開放源碼軟體所廣泛使用的慣例所設計。為了讓這套理論運作,你必須先有定義好的公共 API。這可以透過檔案定義或代碼強制要求來實作。無論如何,這套 API 的清楚明了是十分重要的。一旦你定義了公共 API,你就可以透過修改相應的版本号來向大家說明你的修改。考慮使用這樣的版本号格式:X.Y.Z(主版本号.次版本号.修訂号)修複問題但不影響 API 時,遞增修訂号;API 保持向下相容的新增及修改時,遞增次版本号;進行不向下相容的修改時,遞增主版本号。
我稱這套系統為“語義化的版本控制”,在這套約定下,版本号及其更新方式包含了相鄰版本間的底層代碼和修改内容的資訊。
以下關鍵詞 MUST、MUST NOT、REQUIRED、SHALL、SHALL NOT、SHOULD、SHOULD NOT、 RECOMMENDED、MAY、OPTIONAL 依照 RFC 2119 的叙述解讀。
使用語義化版本控制的軟體必須(MUST)定義公共 API。該 API 可以在代碼中被定義或出現于嚴謹的檔案内。無論何種形式都應該力求精确且完整。
标準的版本号必須(MUST)采用 X.Y.Z 的格式,其中 X、Y 和 Z 為非負的整數,且禁止(MUST NOT)在數字前方補零。X 是主版本号、Y 是次版本号、而 Z 為修訂号。每個元素必須(MUST)以數值來遞增。例如:1.9.1 -> 1.10.0 -> 1.11.0。
标記版本号的軟體發行後,禁止(MUST NOT)改變該版本軟體的内容。任何修改都必須(MUST)以新版本發行。
主版本号為零(0.y.z)的軟體處于開發初始階段,一切都可能随時被改變。這樣的公共 API 不應該被視為穩定版。
1.0.0 的版本号用于界定公共 API 的形成。這一版本之後所有的版本号更新都基于公共 API 及其修改内容。
修訂号 Z(x.y.Z <code>|</code> x > 0)必須(MUST)在隻做了向下相容的修正時才遞增。這裡的修正指的是針對不正确結果而進行的内部修改。
次版本号 Y(x.Y.z <code>|</code> x > 0)必須(MUST)在有向下相容的新功能出現時遞增。在任何公共 API 的功能被标記為棄用時也必須(MUST)遞增。也可以(MAY)在内部程式有大量新功能或改進被加入時遞增,其中可以(MAY)包括修訂級别的改變。每當次版本号遞增時,修訂号必須(MUST)歸零。
主版本号 X(X.y.z <code>|</code> X > 0)必須(MUST)在有任何不相容的修改被加入公共 API 時遞增。其中可以(MAY)包括次版本号及修訂級别的改變。每當主版本号遞增時,次版本号和修訂号必須(MUST)歸零。
先行版本号可以(MAY)被标注在修訂版之後,先加上一個連接配接号再加上一連串以句點分隔的辨別符來修飾。辨別符必須(MUST)由 ASCII 字母數字和連接配接号 [0-9A-Za-z-] 組成,且禁止(MUST NOT)留白。數字型的辨別符禁止(MUST NOT)在前方補零。先行版的優先級低于相關聯的标準版本。被标上先行版本号則表示這個版本并非穩定而且可能無法滿足預期的相容性需求。範例:1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。
版本編譯資訊可以(MAY)被标注在修訂版或先行版本号之後,先加上一個加号再加上一連串以句點分隔的辨別符來修飾。辨別符必須(MUST)由 ASCII 字母數字和連接配接号 [0-9A-Za-z-] 組成,且禁止(MUST NOT)留白。當判斷版本的優先層級時,版本編譯資訊可(SHOULD)被忽略。是以當兩個版本隻有在版本編譯資訊有差别時,屬于相同的優先層級。範例:1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。
版本的優先層級指的是不同版本在排序時如何比較。判斷優先層級時,必須(MUST)把版本依序拆分為主版本号、次版本号、修訂号及先行版本号後進行比較(版本編譯資訊不在這份比較的清單中)。由左到右依序比較每個辨別符,第一個差異值用來決定優先層級:主版本号、次版本号及修訂号以數值比較,例如:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1。當主版本号、次版本号及修訂号都相同時,改以優先層級比較低的先行版本号決定。例如:1.0.0-alpha < 1.0.0。有相同主版本号、次版本号及修訂号的兩個先行版本号,其優先層級必須(MUST)透過由左到右的每個被句點分隔的辨別符來比較,直到找到一個差異值後決定:隻有數字的辨別符以數值高低比較,有字母或連接配接号時則逐字以 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。
這并不是一個新的或者革命性的想法。實際上,你可能已經在做一些近似的事情了。問題在于隻是“近似”還不夠。如果沒有某個正式的規範可循,版本号對于依賴的管理并無實質意義。将上述的想法命名并給予清楚的定義,讓你對軟體使用者傳達意向變得容易。一旦這些意向變得清楚,彈性(但又不會太彈性)的依賴規範就能達成。
舉個簡單的例子就可以展示語義化的版本控制如何讓依賴地獄成為過去。假設有個名為“救火車”的函數庫,它需要另一個名為“梯子”并已經有使用語義化版本控制的包。當救火車建立時,梯子的版本号為 3.1.0。因為救火車使用了一些版本 3.1.0 所新增的功能,你可以放心地指定依賴于梯子的版本号大于等于 3.1.0 但小于 4.0.0。這樣,當梯子版本 3.1.1 和 3.2.0 釋出時,你可以将直接它們納入你的包管理系統,因為它們能與原有依賴的軟體相容。
作為一位負責任的開發者,你理當確定每次包更新的運作與版本号的表述一緻。現實世界是複雜的,我們除了提高警覺外能做的不多。你所能做的就是讓語義化的版本控制為你提供一個健全的方式來發行以及更新包,而無需推出新的依賴包,節省你的時間及煩惱。
如果你對此認同,希望立即開始使用語義化版本控制,你隻需聲明你的函數庫正在使用它并遵循這些規則就可以了。請在你的 README 檔案中保留此頁連結,讓别人也知道這些規則并從中受益。
最簡單的做法是以 0.1.0 作為你的初始化開發版本,并在後續的每次發行時遞增次版本号。
當你的軟體被用于正式環境,它應該已經達到了 1.0.0 版。如果你已經有個穩定的 API 被使用者依賴,也會是 1.0.0 版。如果你很擔心向下相容的問題,也應該算是 1.0.0 版了。
主版本号為零的時候就是為了做快速開發。如果你每天都在改變 API,那麼你應該仍在主版本号為零的階段(0.y.z),或是正在下個主版本的獨立開發分支中。
這是開發的責任感和前瞻性的問題。不相容的改變不應該輕易被加入到有許多依賴代碼的軟體中。更新所付出的代價可能是巨大的。要遞增主版本号來發行不相容的改版,意味着你必須為這些改變所帶來的影響深思熟慮,并且評估所涉及的成本及效益比。
為供他人使用的軟體編寫适當的檔案,是你作為一名專業開發者應盡的職責。保持專案高效一個非常重要的部份是掌控軟體的複雜度,如果沒有人知道如何使用你的軟體或不知道哪些函數的調用是可靠的,要掌控複雜度會是困難的。長遠來看,使用語義化版本控制以及對于公共 API 有良好規範的堅持,可以讓每個人及每件事都運作順暢。
一旦發現自己破壞了語義化版本控制的規範,就要修正這個問題,并發行一個新的次版本号來更正這個問題并且恢複向下相容。即使是這種情況,也不能去修改已發行的版本。可以的話,将有問題的版本号記錄到檔案中,告訴使用者問題所在,讓他們能夠意識到這是有問題的版本。
由于沒有影響到公共 API,這可以被認定是相容的。若某個軟體和你的包有共同依賴,則它會有自己的依賴規範,作者也會告知可能的沖突。要判斷改版是屬于修訂等級或是次版等級,是依據你更新的依賴關系是為了修複問題或是加入新功能。對于後者,我經常會預期伴随着更多的代碼,這顯然會是一個次版本号級别的遞增。
自行做最佳的判斷。如果你有龐大的使用者群在依照公共 API 的意圖而變更行為後會大受影響,那麼最好做一次主版本的釋出,即使嚴格來說這個修複僅是修訂等級的釋出。記住, 語義化的版本控制就是透過版本号的改變來傳達意義。若這些改變對你的使用者是重要的,那就透過版本号來向他們說明。
棄用現存的功能是軟體開發中的家常便飯,也通常是向前發展所必須的。當你棄用部份公共 API 時,你應該做兩件事:(1)更新你的檔案讓使用者知道這個改變,(2)在适當的時機将棄用的功能透過新的次版本号釋出。在新的主版本完全移除棄用功能前,至少要有一個次版本包含這個棄用資訊,這樣使用者才能平順地轉移到新版 API。
沒有,請自行做适當的判斷。舉例來說,長到 255 個字元的版本已過度誇張。再者,特定的系統對于字串長度可能會有他們自己的限制。
語義化版本控制的規範是由 Gravatars 創辦者兼 GitHub 共同創辦者 Tom Preston-Werner 所建立。
如果您有任何建議,請到 GitHub 上提出您的問題。
知識共享 署名 3.0 (CC BY 3.0)
語義化版本 2.0.0 | Semantic Versioning (semver.org)