版本的故事(四)版本號有多重要

上一篇談了怎樣給版本取個好名字,也就是版本號。說明了「語義化版本」的命名規範,也說明了這一種命名規範在依賴管理中發揮的重要做用。今天繼續談語義化版本號,說明一下這種命名方式的重要性,以及對研發和運維過程的影響。web

開發人員每一次向下遊工序交付一個版本,都必須爲這個版本編一個號碼,這就是版本號(也稱做版本 ID)。若是每次交付都是一樣的版本號,隨着時間的推動就會產生不少版本號相同、可是功能不同的二進制包,在這種狀況下部署,你能夠想象會遇到多少驚奇。對於每一次生成二進制包,都應該分配一個惟一標識,對於審計來講,這是很是重要的。最顯而易見的方法,是使用二進制包的散列值做爲惟一標識,以即可以驗證生成二進制包的源代碼是否正確。當你不肯定某個環境到底部署了哪一個版本的時候,可使用文件的 MD5 碼找出版本庫中對應的版本。一些二進制產物管理平臺能夠自動提取散列碼。可是以散列碼做爲標識有一些缺點,很明顯的一個缺點是散列碼太長,與他人溝通的時候很難記得住(你很難告訴同事「把 customer 組件從 ea3304ea2fff21dd1e501795c43c48ff 版本替換成 81b134b598b16d1605e6f76189f1c018 版本就能夠解決你遇到的問題」,他也很難記住)。更重要的是散列碼只能標識兩個版本是否相同,卻沒法體現版本之間的時間關係(哪個版本是新的)和兼容性(新版本是否徹底包含老版本的功能)。因此咱們應該使用規範化的版本號做爲二進制包的標識符。網絡

因此咱們必須採用語義化版本規範,使用三段式版本號,以下:數據結構

主版本號.次版本號.修訂號

版本號遞增規則以下:運維

  • 主版本號:當作了不兼容的 API 修改,
  • 次版本號:當作了向下兼容的功能性新增,
  • 修訂號:當作了向下兼容的問題修正。

使用這種方式,版本號就再也不是一個隨意的命名。任何一個以 API 方式對外提供服務的程序(不管是 Web API、消息處理、函數 API),都應該遵循語義化版本規範。從 API 規範的意義上說,語義化版本其實是對軟件接口規格的描述。例如一個軟件模塊對外提供 Web API 服務,模塊版本是 2.3.1,能夠理解成這樣的規格描述:函數

爲了準確描述程序的接口規範,咱們在設計和開發的時候要儘可能把接口規範和實現代碼分離,這樣就能夠更準確的控制 API 規格。以 Web API 爲例,咱們能夠把與服務接口相關的代碼放在單獨的目錄裏,好比把控制器代碼所有放在 controller 目錄,輸入輸出數據結構所有在 vo 目錄。這樣就能夠在發佈版本的時候根據變動的範圍準確肯定版本號。工具

當如下變動發生時,接口的調用方法發生了變化,須要升級主版本號:ui

  • controller 刪除了原有接口;
  • controller 在原有接口上添加了參數,而且參數是必須的;
  • vo 刪除了輸出數據的屬性;
  • vo 添加了輸出數據的屬性,而且屬性是必須的。

當如下變動發生時,原有的接口仍然能夠工做,須要升級次版本號:spa

  • controller 添加了新接口;
  • controller 在原有加快上添加了參數,可是參數能夠不輸入;
  • vo 添加了輸出數據的屬性;
  • vo 添加了輸入數據的屬性,可是屬性能夠不輸入。

當如下變動發生時,只須要升級修訂號:操作系統

  • controller 和 vo 的代碼都沒有改動,只改動了程序其餘的部分。

用這樣的方法,版本號就能夠描述程序內部的變動範圍。 設計

下面說一下版本號對依賴管理的做用。在構建和運行軟件時,軟件的一部分要依賴於另外一部分,就產生了依賴關係。在任何應用程序(甚至是最小的應用程序)中也會有一些依賴關係。至少,大多數軟件應用都對其運行的操做系統環境有依賴,Java 應用程序依賴於 JVM,它提供了 JavaSE API 的一個實現。網絡服務之間也存在依賴關係。在大型軟件中,從組件中選擇好用的版本,組成一個完整的系統是一個極具難度的事。爲了作好依賴管理,咱們必須作下面幾件事:

  1. 爲每個二進制包制定惟一的版本號,禁止一物多碼,更要禁止一碼多物。必須標識版本,才能管理依賴;
  2. 發佈版本時描述依賴關係;
  3. 使用語義化版本號,只要肯定了一個版本是可用的,就能夠肯定一個區間的版本均可用。

咱們以 Linux 操做系統爲例看一下依賴管理的過程。Linux 是一個很是複雜的體系,它自己由不少二進制包組成,使用者也須要在操做系統上安裝本身須要的程序。若是沒有一個依賴管理機制,要在 Linux 上安裝一個軟件,將會是一件困難的任務。幸運的是各類 Linux 發行版都提供了完善的包管理機制,還附帶了包管理工具。好比 Debian 操做系統,提供了 dpkg 工具,如下是使用 dpkg 查看 wget 信息:

$ dpkg -s wget
Package: wget
Section: web
Maintainer: Noël Köthe <noel@debian.org>
Architecture: amd64
Version: 1.18-5+deb9u3
Depends: libc6 (>= 2.17), libgnutls30 (>= 3.5.6), libidn11 (>= 1.13), libnettle6, libpcre3, libpsl5 (>= 0.13.0), libuuid1 (>= 2.16), zlib1g (>= 1:1.1.4)

這裏列出了主要信息,有兩個信息很是重要:

  • wget 自己的版本號:1.18-5+deb9u3
  • wget 依賴的其餘組件版本(Depends 行)

有了這些信息,就能夠在安裝 wget 的時候檢查 Debian 上已經安裝的庫,判斷是否知足依賴條件,包管理工具能夠級聯安裝全部的依賴項。也能夠檢查 wget 與已經安裝的程序是否存在依賴衝突,提示用戶進行處理。若是沒有這一套包管理機制,在 Linux 上安裝一個包是很是冒險的事情。

最後咱們再來看看語義化版本是怎樣幫助咱們作好老版本維護的。有時候正在生產環境運行的老版本突然發現一個缺陷,或者須要添加一個功能,都須要對老版本進行維護。這種事情在 To B 業務很是多見,To B 業務部署在不少現場,每一個現場項目實施的時期不同,因此版本都有一些差別,對老版本進行維護是一件不可避免的事情。

若是不使用語義化版本號,好比用一個不斷增加的序號來標識版本號,連續發佈多個版本就會造成這樣的版本路徑:

 

 

隨着時間的發展,有一些老版本會在部署在不一樣的現場。如今 1002 版本上發現一個缺陷,須要緊急修復。這時候該怎麼辦呢?1002 版本已經通過了 2 次升級,直接替換成 1004 版本行不行,很難判斷,因此只能基於 1002 版本升級替換。這個好辦,使用 Git 作一個分支,修改後從新發佈一個版本就能夠了。缺陷修改後,造成下面這樣的版本路徑:

 

 

 

若是之後須要維護 100一、1003 版本,繼續發展下去,版本路徑就會愈來愈複雜:

 

 

維護版本分支愈來愈多,基本上要爲每個老版本建立一個維護分支,工做量隨着項目發展愈來愈大。開發團隊要把大量的精力放在老項目維護上,產品開發的工做受到愈來愈多的牽制。語義化版本號能怎樣改變這種局面呢?若是每一次發佈都按照語義化版本編號,那麼最初的版本路徑就是下面這樣:

 

 

用這種方式,咱們就能準確判斷版本之間的兼容關係,根據版本之間的替換關係肯定最佳維護位置。當咱們在 1.0.6 版本上發現一個缺陷,須要緊急修復,1.2.1 版本能夠徹底兼容 1.0.6 版本的功能。若是這個缺陷已經在 1.2.1 版本獲得修復,那麼升級現場的版本便可。若是必須修改代碼,也只須要在 1.2.1 的基礎上修改,再發佈一個版本便可,不增長維護分支:

 

 

 

若是已經發生了主版本升級的狀況,咱們也只須要爲每個主版本建立一個維護分支,就能同時知足多個項目的維護工做,下降維護工做量。

 

 

如上圖,對於全部 1.x 主版本,只須要基於 1.2.2 版本創建一個維護分支便可。

相關文章
相關標籤/搜索