方舟原始恐惧代码_源代码分支管理模式丨中国DevOps社区
Martin Fowler
現代的源代碼控制系統提供了強大的工具,可以非常輕松的在源代碼上創建分支。但最終分支還是要合并在一起,許多團隊不得不花相當多的時間去處理相互糾纏的分支。這里有幾種模式讓團隊可以有效地使用分支,專注于集成多個開發人員的工作并組織產品發布的路線。最重要的一點,分支應該頻繁集成,盡力保持一個無需過多干預就可部署生產的健康主線。
2020年5月28日
目 錄1. 基本模式?
1.1 源分支 ??
1.2 主線 ??
1.3 健康的分支 ??
2. 集成模式?
2.1 主線集成??
2.2 特性分支開發??
2.3 集成頻率?
2.3.1?低頻集成?
2.3.2?高頻集成?
2.3.3?集成頻率對比?
2.4 持續集成??
2.5 對比特性分支開發和持續集成
2.5.1 特性分支開發和開源
2.6 對提交評審???
2.7 集成沖突?
2.8 模塊化的重要性?
2.9 個人對于集成模式的看法?
3.?從主線到生產發布的路徑
3.1 發布分支??
3.2 成熟度分支??
3.2.1 變體:長期存在的發布分支
3.3 環境分支??
3.4 補丁分支??
3.5 發布火車??
3.5.1 變體:載入未來的火車
3.5.2 與主線外的常規發布比較
3.6 準備發布的主線 ?
4. 其它分支模式
4.1 實驗分支 ?
4.2 未來分支??
4.3 協作分支??
4.4 團隊集成分支??
5. 考慮一些分支策略
5.1 Git-flow
5.2 GitHub Flow
5.3 基于主干開發
6. 最后的想法和建議
7. 邊欄
7.1 集成恐懼
7.2 持續集成和基于主干開發
對任何軟件開發團隊來說,源代碼都是重要的資產。幾十年來,已有一系列源代碼管理工具被開發出來,用于維護代碼。這些工具可以跟蹤變更,因此我們可以恢復軟件的歷史版本并查看它的演進過程。這些工具還是開發團隊的協作中心,團隊中的所有程序員都在一個公共的代碼庫上工作。通過記錄每位開發人員所做的更改,這些系統可以一次跟蹤多行工作內容,并幫助開發人員解決如何把這些內容合并到一起。
將開發活動劃分為分解和合并的工作流,是軟件開發團隊工作流程的核心,并且已演化出多種模式幫助我們處理所有這些活動。像大多數軟件模式一樣,幾乎沒有哪種模式是所有團隊都應遵循的黃金法則。軟件開發工作流程依賴于具體環境,特別是團隊的社會結構和團隊遵循的其他實踐。
本文將詳述這些模式,并在模式描述中夾雜可以更好地說明模式背景和相互關系的敘事部分。為便于區分,模式描述的章節將附以圖標“?”。
基本模式在思考這些代碼分支模式時,我發現它們可以分為兩大類。一類模式著眼于集成,即多個開發人員如何將他們的工作成果組合成一個連貫的整體。另一類則著眼于生產路徑,即使用分支幫助管理從集成代碼庫到生產環境運行產品的路徑。一些模式為這兩大類模式提供支撐,我將它們歸類為基本模式,在本節中講述。還有一些模式既不基本也不適合于歸類到集成和生產路徑這兩大類模式,我把它們留到最后來講。
? 源分支 ?創建一個副本并記錄對該副本的所有更改。
如果幾個人在同一代碼基礎上工作,那么很快他們就無法在相同文件上工作。如果我想運行一個編譯,而我的同事還正在敲入一個表達式,那么編譯將失敗。我們不得不相互呼喊:“我正在編譯,什么都不要更改!”即使團隊只有兩個人,這也難以維持正常工作;如果是更大的團隊,這種混亂場景會更加令人難以想象。
對此場景案例的簡單解決辦法是讓每個開發人員都獲取一個代碼庫的副本,然后我們就可以輕松地進行自己負責的功能開發。但是又會出現一個新問題:開發完成后,如何將兩個副本再次合并在一起?
源代碼控制系統使此過程更加容易。關鍵在于它會將每個分支上所有的更改都記錄為提交。這不僅可以確保沒有人忘記他們對 utils.java 所做的微小更改,而且記錄更改使執行合并更加容易,尤其是當幾個人更改了同一文件時。
這就引出了本文中使用的分支(branch)的定義。我將分支定義為對代碼庫的特定提交序列。分支的 head 或 tip 指向該序列中的最新提交。
分支是個名詞,但也有動詞“ 創建分支”的意思。這里我的意思是創建一個新分支,我們也可以將其視為將原始分支分為兩個分支。當來自一個分支的提交被應用到另一分支時,即為分支合并。
我用于“分支”的定義與我觀察大多數開發人員談論它們的方式相對應。但是源代碼控制系統更傾向于以特定的方式使用“分支”。
以一種常見情況來說明這一點,一個現代開發團隊,該團隊將其源代碼保存在共享的 git 倉庫中。一名開發人員 Scarlett (以猩紅色表示) 需要進行一些更改,因此她克隆了 git 倉庫并檢出了 master 分支。她做了幾處更改,然后重新提交給她的 master 分支。同時,另一個開發人員,Violet (以紫色表示) 將倉庫克隆到自己桌面上,并簽出 master 分支。那么 Scarlett 和 Violet 是在同一個分支上工作還是分別在另一個分支上工作?答案是:他們都在 “master” 上工作。但是他們的提交彼此獨立,并且當他們將更改推回到共享倉庫時都需要合并。如果 Scarlett 不確定自己所做的更改,會發生什么情況,因此她標記了最后的提交,并將她的 master 分支重置回 origin/master(她克隆共享倉庫時的最后一次提交)。
根據我前文給出的分支定義,Scarlett 和 Violet 分別在單獨的分支上工作,這兩個分支彼此分開,并且與共享倉庫上的 master 分支隔離。當 Scarlett 放棄帶有標簽的分支開發時,根據定義,它仍然是一個分支(并且她很可能將其視為分支),但是在 git 看來,這是一個帶標簽的代碼行。
使用 git 這樣的分布式版本控制系統,這意味著每當我們進一步克隆倉庫時,就會獲得其他分支。如果 Scarlett 在回家的火車上克隆了自己的本地倉庫到筆記本電腦上,那么她將創建第 3 個 master 分支。在 GitHub 中派生也會產生相同的效果 —— 每個派生的倉庫都有自己額外的分支集。
當我們遇到不同的版本控制系統時,這種術語的混亂會變得更糟,因為它們對分支的構成都有自己的定義。Mercurial 中的分支與 git 中的分支完全不同,后者更接近 Mercurial 的書簽。Mercurial 也可以用未命名的 head 創建分支,使用 Mercurial 的人們經常通過克隆倉庫來創建分支。
所有這些術語上的混亂導致一些人避免使用該術語。在這里更通用的術語是代碼線(CodeLine)。我將代碼線定義為代碼庫的一系列特定版本。它可以以標簽結尾,或是一個分支,又或者淹沒在 git 的 reflog 中。你會注意到我對分支和代碼線的定義是如此相似。代碼線在許多方面都是更有用的術語,我確實使用過,但是在實踐中并未廣泛使用。因此,對于本文而言,除非我處于 git(或其他工具)術語的特定上下文中,否則我將交替使用分支和代碼線。
此定義的結果是,無論你使用的是哪種版本控制系統,一旦有開發人員在進行本地更改后,每個開發人員在本地的工作副本中都至少具有一條個人代碼線。如果我克隆一個項目的 git 庫,檢出 master 分支并更新一些文件 —— 這就是一條新的代碼線,即使我還沒有提交任何內容。同樣,如果我從 subversion 庫的主干建了自己的工作副本,即使不涉及任何 subversion 分支,該工作副本也是獨立的代碼線。
適用場景
一個老話說,如果你從高樓上摔下來,墜落不會傷害到你,但是著陸會。對源代碼來說也是一樣的道理:創建分支容易,但合并困難。
記錄提交中所有更改的源代碼控制系統確實讓合并過程更加容易,但并沒有使合并過程不再重要。如果 Scarlett 和 Violet 都將變量的名稱更改為不同的名稱,則存在沖突,如果沒有人工干預,源管理系統將無法自行處理。為了凸顯這種文本沖突的尷尬,源代碼控制系統至少還可以發現并提醒人們看一下。但是在文本合并沒有問題的地方也經常會出現沖突,系統仍然無法正常工作。想象一下,Scarlett 更改了函數的名稱,而 Violet 向其分支添加了一些代碼,以其舊名稱調用該函數。這就是我所說的語義沖突。當發生此類沖突時,系統可能無法構建,也可能會構建成功但在運行時失敗。
Jonny LeRoy 喜歡指出人們(包括我)繪制分支圖的這個瑕疵
任何有并行計算或分布式計算工作經驗的人都熟悉的問題是:當多個開發人員同時更新時,代碼倉會處于某個共享狀態。我們需要通過將這些更新序列化為某個共識更新的方式,把這些開發人員的更新結合起來 。事實上,使系統正確執行和運行意味著該共識狀態的有效性標準非常復雜,這使我們的任務也變得更加復雜。無法創建確定性算法來找到共識。人們需要尋求共識,并且共識可能涉及混合不同更新的選擇部分。通常,只有通過原始更新解決沖突才能達成共識。
我說:“如果沒有分支該怎么辦”。每個人都將實時編輯代碼,考慮不周的更改會使系統崩潰,人們會互相踩踏。因此,我們給個人一種時間凍結的錯覺,認為他們是唯一更改系統的人,這些變更可以等到他們對系統風險考慮充分后才變更。但這是一種錯覺,最終代價還是該來的會來。誰買單?什么時候?代價是多少?這些模式正在討論的就是:選擇如何支付代價。—— Kent Beck因此,在下文中我將列出各種模式,這些模式支持友好的隔離,就像當你從高處落下時,風穿過發絲,同時又把不可避免的與堅硬地面的碰撞后果降到最低。
? 主線 ?單一、共享、代表產品當前狀態的分支
主線(mainline)是一個特殊的代碼線,代表團隊代碼的當前狀態。當我想開始一項新工作,我會從主線中拉取代碼到我的本地版本庫,在本地版本庫上工作。當我要與團隊的其他成員分享我的工作成果時,我會用我的工作成果更新主線,理想狀態下將應用后面要討論的主線集成模式。
不同的團隊使用不同的名稱稱呼這一特殊分支,通常會受使用的版本控制系統慣例的影響。Git 用戶通常稱之為 “master”, subversion 用戶通常稱之它為 “主干”。
在這里必須強調,主線是一個單一的、共享的代碼線。當人們在 git 中談論 “master” 時,他們可能在說幾件不同的事情,因為每個代碼庫的克隆都有自己的本地 master。通常,團隊會有一個中央倉庫 —— 一個作為項目單一記錄點的共享倉庫,并且是大多數克隆的起源。從頭開始一項新工作意味著克隆該中央倉庫。如果已經有了一個克隆,我會首先從中央倉庫拉取 master 分支,以保持與主線同步。在這種情況下,主線就是中央倉庫的 master 分支。
當我在開發自己的功能時,我在使用自己的開發分支,這個分支可以是我本地版本庫的 master 分支,也可以是其他本地分支。如果需要在自己的開發分支上工作較長時間,我可以每隔一段時間拉取主線的更改,并把這些更改合并到我自己的開發分支上,以獲取主線上最新的更改。
同樣,如果我想創建產品發布的新版本,我可以從當前主線開始。如果我需要修復錯誤,以發布足夠穩定的產品,我可以使用某一發布分支。
適用場景
我記得在 21 世紀初常和一個客戶端構建工程師討論。他的工作是集成團隊正在開發的產品。他會給團隊的每個成員發一封電子郵件,團隊成員則會發回各自代碼庫中等待集成的各種不同文件。這位構建工程師就把這些文件復制到他的集成樹中,并嘗試編譯代碼庫。創建一個能夠編譯,并可供某種形式進行測試的構建,通常需要耗費這位構建工程師幾周的時間。
相比之下,通過主線,任何人都可以從主線的一部分快速開始產品最新的構建。更重要的是,主線不僅僅使得觀察代碼庫狀態更容易,它還是許多其他模式的基礎,這些模式將后文中描述。
主線的一個替代方案是發布火車。
? 健康的分支 ?在每次提交時執行自動檢查,以確保分支沒有缺陷,自動檢查通常包括構建和運行測試
由于主線具有共享的并且是已被認可的狀態,因此保持主線處于穩定狀態非常重要。還是在 21 世紀初,我記得曾和某一組織的一個開發團隊一起討論,這個組織因對所有產品執行每日構建而廣為人知。在當時,每日構建被認為是相當先進的做法,這個組織也因此而獲得贊譽。在這些贊揚的文章中沒有提到的是,那些每日構建并不總是成功的。實際上,一些團隊的日常構建連續數月都無法編譯成功,這在當年并不罕見。
為了解決這個問題,我們可以努力去保持一個分支是健康的——也就是這個分支是可以成功構建并且運行時幾乎沒有 bug 的。為了確保這一點,我發現編寫自測代碼是至關重要的。這種開發實踐是指我們在編寫生產代碼時,還要編寫一套全面的自動化測試,讓我們可以確信,如果這些測試通過,那么這些代碼就不會有 bug。如果我們這樣做,就可以通過每次提交運行一個構建來保持分支健康,這個構建過程也包括運行這套測試。如果系統無法編譯,或者測試失敗,那么我們的第一要務就是在我們對該分支進行任何其他操作之前就先對其進行修復。通常這意味著我們“凍結”了這個分支——除為了修復以使其恢復正常的提交之外,不會允許在這個分支進行任何提交。
為了給保持分支健康提供足夠的信心,在測試的程度上存在一定矛盾。許多更徹底的測試需要大量的時間去運行,這就會延遲對提交是否正常的反饋。一些團隊通過將測試分散到部署流水線的多個階段來解決這個問題。這些測試的第一個階段應運行快速,一般不超過十分鐘,但仍應相當全面。我將這樣的測試集稱為提交套件 (不過它通常會被稱為“單元測試”,因為這樣的提交套件中的測試大多數是單元測試)。
理想情況下,應在每次提交時運行全方位的測試。但是,如果測試執行很慢,例如需要占用服務器幾個小時的性能測試,那就有點不切實際。如今,團隊通常會構建一個提交套件,在每次提交時運行,而對部署流水線后續的階段,會盡可能頻繁地運行。
代碼運行沒有錯誤并不足以說明就是好的代碼。為了保持穩定的交付節奏,我們需要保持足夠高的代碼內建質量。一種流行的方法是使用提交審核(Reviewed Commits),然而我們也要看到還有其他選擇。
適用場景
每個團隊都應當在他們的開發工作流程中明確每個分支的健康狀況標準。保持主線健康有無比重要的價值。如果主線是健康的,那么開發人員只要從當前的主線拉取代碼就可以開始新的工作,而不會糾結于那些可能會妨礙他們工作的缺陷。我們經常聽說有人在開始新的工作前要花幾天時間去嘗試修復或繞過他們拉取代碼中的問題。
健康的主線也可以簡化生產路徑。可以隨時從主線的最新版本構建新的生產候選對象。最好的團隊發現他們幾乎不需要做任何工作來穩定這樣的代碼庫,這些代碼庫通常能夠直接從主線發布到生產環境。
主線健康的關鍵是自測代碼,以及一個可在幾分鐘內運行完成的提交套件。建設這樣的能力會是很有意義的投入,一旦我們可以在幾分鐘之內確保我的提交不會搞砸任何東西,那將徹底改變我們的整個開發過程。我們可以更快地進行更改,自信地重構我們的代碼讓它更好用,并大大減少從期望功能到生產中運行代碼的交付周期。
保持個人開發分支的健康是明智的做法,因為這樣可以啟用差異調試。但是,這種期望和頻繁提交當前狀態為檢查點是背道而馳的。如果我要嘗試一個不同的路徑,那么即使編譯失敗可能也會去創建一個檢查點。解決這種矛盾的方法是,一旦完成我最近的工作,就去除所有不健康的提交。這樣,只有健康的提交會在我的分支上保留超過幾個小時。
如果我保持個人分支的健康,這也能使提交到主線變得更加容易——我會知道任何在主線集成(Mainline Integration)中突然出現的錯誤都純粹是由于集成問題引起的,而不單單是我代碼庫中的錯誤。這將使查找和修復錯誤變得更快也更容易。
未完待續……
(下一篇將介紹集成模式)
- End -
往期文章
Serverless無服務器架構之未來篇
Serverless無服務器架構之優缺點
Serverless無服務器架構之概念篇
重溫經典:持續集成(下篇)
重溫經典:持續集成(上篇)
第14屆年度敏捷狀態調查報告
2020年DevOps工具報告
DevOps:IT領導者指南
什么是 DevOps?DevOps 終極指南(下)
什么是 DevOps?DevOps 終極指南(上)
SRE 團隊的組織方式以及入門方法
DevOps 幫助數字化轉型的10個最佳實踐
GOOGLE 事后回顧檢查清單【文檔模板】
想成為譯者?
掃碼加入我們
社區譯者團
活動預告
線上活動
??? 中國DevOps社區北京第八屆線上Meetup:7月25日
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的方舟原始恐惧代码_源代码分支管理模式丨中国DevOps社区的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人脸识别代码_Python人脸识别源代码
- 下一篇: hash 值重复_“重复”相关的问题