作者 | 鄭茗蔓
問題的提出
任何復(fù)雜的軟件都是團隊工作的產(chǎn)物,所以我們會利用版本控制工具和不同的分支策略來協(xié)助團隊的日常開發(fā)和交流,mainline開發(fā)模式和pull request開發(fā)模式(以下簡稱PR)則是最常用到的兩種模式。在開發(fā)時選擇哪種模式也成了一個經(jīng)常被討論的話題。
在疫情時代,遠(yuǎn)距離辦公可能會阻礙團隊的交流,PR開發(fā)模式也變得越來越流行。一方面PR開發(fā)模式可以為代碼開發(fā)帶來更好的隔離性,但另一方面,PR開發(fā)模式其實是一種更難掌握或者說要求更高的開發(fā)模式。比如:審查和合并 PR 的速度至少取決于三個因素:上下文、大小和原子性。此外,PR開發(fā)模式對重構(gòu)不是很友好。因為重構(gòu)需要高頻率的集成來盡早發(fā)現(xiàn)和解決引入的沖突,但在PR開發(fā)模式之下是比較難做到的。

相比之下,mainlina開發(fā)模式是我更為傾向的一種實踐。首先,它不需要考慮太多額外的因素。開發(fā)人員只需要有了“健康”的commit之后,就能與mainline集成,將自己的代碼變更在團隊中可視化。此外,它對重構(gòu)有很好的支持,因為mainline開發(fā)模式本身就支持持續(xù)集成。
《Software Configuration Management Patterns》(以下簡稱SCM patterns)一書從軟件配置管理的角度出發(fā),關(guān)注那些會影響代碼編寫、功能實現(xiàn)以及代碼更改等日常工作的各個方面,并將其總結(jié)成一系列模式。
下面,本文想要從SCM patterns的視角來對比一下mainline開發(fā)模式和PR開發(fā)模式。
概念的厘清
為了更好地理解這兩種開發(fā)模式,以及將其進行對比,有必要先對相關(guān)的基本概念以及兩種模式的概念進行簡要的說明。
Codeline和Codeline Policy組合差異決定開發(fā)模式的不同
不同的codeline和codeline policy的組合可以形成不同的開發(fā)模式。從形式上看,mainline開發(fā)模式和PR開發(fā)模式的區(qū)別,其實是codeline和codeline policy的不同。因此,我們有必要先了解一下這二者的基本概念。
(1) Codeline
codeline其實就是我們常說的branch(Brad Appleton在這里有做說明),在SCM patterns中,codeline的定義如下:
A codeline is a progression of the set of source files and other artifacts
that make up some software component as it changes over time. Every time you
change a file or other artifact in the version control system, you create a
revision of that artifact.
(2) Codeline Policy
codeline policy實際上是對于codeline的使用手冊,為每一條codeline持續(xù)運行提供了保障機制,也能夠讓開發(fā)人員更加明確的知道:應(yīng)該將代碼簽入哪個codeline、何時簽入以及在簽入前要運行哪些測試。每個codeline都會相應(yīng)地有一個codeline policy。
舉個例子:
Development codeline:可以簽入臨時代碼,但相關(guān)組件需要是可以構(gòu)建的。
- Mainline:所有組件必須編譯和鏈接,并通過回歸測試;已完成并且經(jīng)過測試的新功能可以簽入。
- Release codeline:軟件必須在簽入前構(gòu)建并通過回歸測試;簽入的代碼僅限于錯誤修復(fù);不得簽入新特性或功能;簽入后,分支被凍結(jié),直到整個QA 周期完成。
總的來說,不同的codeline在不同的項目中,有不同的目的和不同的codeline policy,同時也代表著不同的穩(wěn)定程度,比如:active development codeline是以快速開發(fā)為主,穩(wěn)定程度足夠開發(fā)就好;而release codeline則是一個完全測試,必須保持足夠穩(wěn)定的codeline。相比之下,release codeline對穩(wěn)定程度的要求則高很多。
對待mainline和active development line的方式?jīng)Q定兩種開發(fā)模式的不同
了解完上面的基本概念之后,下面我們來正式認(rèn)識一下這兩種不同的開發(fā)模式。mainline開發(fā)模式和PR開發(fā)模式兩者明顯的區(qū)別是對待mainline和active development line的方式不同。
(1) Mainline
mainline是一個特殊的codeline,通常它被認(rèn)為是代表了團隊代碼的當(dāng)前狀態(tài),用作子分支及其合并的基礎(chǔ)。Martin Flower是這樣描述mainline的:
A single, shared, branch that acts as the current state of the product
需要注意的是mainline是一個codeline,而mainline開發(fā)模式是一種development model。
(2) Active development line
active development line是開發(fā)人員開發(fā)代碼所使用的codeline/branch。但這個codeline的特點是:足夠穩(wěn)定,能保證開發(fā)即可。
(3) Mainline開發(fā)模式
mainline開發(fā)模式是一種開發(fā)人員在mainline上直接進行開發(fā)工作的模式。此時,mainline = active development line。在SCM patterns中,作者是這樣描述mainline development model的:
When you are developing a single product release, develop off of a mainline.
A mainline is a “home codeline” that you do all your development on except in
special circumstances.

(4) Pull request 開發(fā)模式
PR開發(fā)模式則是開發(fā)人員在分支上進行開發(fā),然后通過pull request的方式,將分支合并回mainline的一種開發(fā)模式。此時,mainline != active development line。

兩種模式的對比
通過上面對兩種開發(fā)模式的介紹,我們知道兩者一個明顯的區(qū)別是mainline和active development line是否為同一codeline。那么接下來,我想通過集成設(shè)計、codeline穩(wěn)定程度的變化和出發(fā)點這三個方面來對兩種開發(fā)模式進行對比。
mainline開發(fā)模式的集成設(shè)計比PR開發(fā)模式更為簡單
首先,我們來看codeline和CI設(shè)計的問題。
在mainline開發(fā)模式中:mainline = active development line
如果需要一個非常穩(wěn)定的代碼線(stable codeline)以做發(fā)布相關(guān)或已發(fā)布的bug修復(fù)等工作的時候,通常會切一個release codeline,此時:
release codeline = stable codeline
在PR開發(fā)模式中:feature branch = active development line
注:feature branch在這里指PR所對應(yīng)的那個分支。
通常,采用PR開發(fā)模式其實就是因為mainline需要非常穩(wěn)定,所以此時:
mainline = stable codeline
因此,這兩種模式對mainline的穩(wěn)定程度要求是不同的,mainline開發(fā)模式對mainline的穩(wěn)定程度要求是低于PR開發(fā)模式的。此外,codeline和CI的聯(lián)系是非常緊密的,因為CI的觸發(fā)來自于代碼的改變,而代碼的改變來自于特定codeline的commit。所以,在考慮設(shè)計我們的CI的時候,同樣也需要考慮如何設(shè)計我們的codeline。在SCM patterns的視角下,就是對private workspace的要求不同。

注:圖片來源于《Software Configuration Management Patterns》官方網(wǎng)站
在SCM patterns這張圖中,我們可以看到:private workspace模式的"Complete with"需要integration build和private system build等。而integration build和private system build都需要一系列測試的支持(smoke test、unit test、regression test)。
在mainline開發(fā)模式中,開發(fā)人員通常是在本地完成開發(fā)并通過了codeline policy的提交要求之后,直接將代碼合并到mainline。這個過程中,開發(fā)人員會進行頻繁地集成,與mainline的集成通常是在一天之內(nèi)。由于我們的active development line(mainline)在遠(yuǎn)端只有一條,只有當(dāng)我們進行mainline上的提交的時候,才會觸發(fā)CI的運轉(zhuǎn)。這個時候,我們CI數(shù)量等于mainline的數(shù)量,其實也就是1條CI。
但在PR開發(fā)模式中,開發(fā)人員通常是在PR被approve之后,才能對mainline進行集成,因此集成時間通常是好幾天甚至一兩周。這個時候,由于mainline = stable line,但因為feature branch不會對mainline頻繁集成,為了避免feature branch在集成時對stable line引入嚴(yán)重的錯誤,因此也會要求feature branch能夠進行更多的測試。但更多的測試也往往意味著更多的開銷,為了讓本地開發(fā)高效進行,通常會把build和test放到CI上去。因而在PR開發(fā)模式下,通常是一個PR(feature branch/active development line)對應(yīng)一個CI,這個時候,我們CI數(shù)量等于mainline + feature branch的數(shù)量。
由此可見,mainline開發(fā)模式和PR開發(fā)模式對CI的設(shè)計是不同,其根本在于codeline的穩(wěn)定程度影響著對private workspace的要求。
注:把mainline上的代碼持續(xù)合并代碼到PR的active development codeline不叫集成,因為集成是pull和push雙方向,Martin Flower的《Patterns for Managing Source Code Branches》文章里也有對此做說明。
mainline開發(fā)模式中mainline的穩(wěn)定程度在持續(xù)集成下比PR開發(fā)模式更容易發(fā)生變化
雖然在mainline開發(fā)模式下,mainline = active development line,我們對mainline的穩(wěn)定程度要求可能不如stable line/release line這么高。但由于頻繁持續(xù)的集成,其實mainline/active development line的穩(wěn)定程度是會發(fā)生改變并且趨向于穩(wěn)定的,而在PR模式下,active development line和mainline的穩(wěn)定程度則不會一起變化。
在進行對比之前,我們先來看Laura Wingerd在她的書《Practical Perforce》中提出的一種“tofu”模型?!皌ofu”模型是對codeline的穩(wěn)定性和質(zhì)量的一種非正式評估。“tofu”模型會根據(jù)以下四個方面來對codeline進行度量:
- How close software is to being released
- How rigorously changes must be reviewed and tested
- How much impact a change has on schedules
- How much a codeline is changing

注:圖片來源于《Practical Perforce》
從圖上我們能看見,release codeline在“tofu”模型上處于最高(firm),通常來說,它不會有太大的變化,也會有嚴(yán)格的審查和測試要求,即使是輕微的變化也可能會影響發(fā)布計劃。mainline是中等,代碼的更改會需要通過測試,但離發(fā)布時間更遠(yuǎn),對發(fā)布計劃的影響處于中等。development codeline處于最低(soft),往往會變化迅速,離軟件發(fā)布最遠(yuǎn),甚至可能還沒有針對其最新開發(fā)的測試。
現(xiàn)在回過頭來看我們這兩種開發(fā)模式。
在PR開發(fā)模式中,mainline和active development line本身就是兩條codeline,對應(yīng)的policy也不相同。這里或許會說,我將mainline的policy應(yīng)用到active development line上,試圖提升active development line在“tofu”模型中的位置,從而間接提升mainline的位置。但其實開發(fā)人員始終是在active development line上開發(fā),而非mainline。在active development line沒有被集成到mainline之前,它們始終是兩條codeline。另外,PR開發(fā)模式和持續(xù)集成也是割裂的,因為PR決定了集成頻率的上限,只有在PR被approve之后才能被集成到mainline中。這種割裂則讓PR開發(fā)模式下的mainline難以享受持續(xù)集成帶來的好處,比如:更早的發(fā)現(xiàn)和解決問題以減少風(fēng)險。因此,在PR開發(fā)模式下,mainline和development line始終會是處于一高(mainline)一低(development line)的狀態(tài),無論development line多么靠近mainline。
但在mainline開發(fā)模式中,mainline和active development line是同一條codeline。那么在“tofu”模型中,由于是同一條codeline,這也就意味著將development line/mainline的上限從mainline變成了release line。其次,由于mainline開發(fā)模式本身對持續(xù)集成就有著很好的支持,其穩(wěn)定程度也在不斷地集成中趨向于穩(wěn)定。因為代碼的更改會非常頻繁地集成到mainline中,每一次的合并實際上都是對mainline質(zhì)量的一次考驗。此外,如果大家能堅持執(zhí)行“一旦有失敗的commit,所有開發(fā)人員的第一優(yōu)先級是修復(fù)錯誤并且不能向mainline提交commit”的理念,那么mainline的質(zhì)量會趨向于維持在一個較高的水平。
mainline開發(fā)模式的出發(fā)點比PR開發(fā)模式更有利于團隊的長期發(fā)展
mainline開發(fā)模式和PR開發(fā)模式都需要維護代碼質(zhì)量,但這兩種模式卻又有著不同的出發(fā)點。mainline開發(fā)模式的出發(fā)點是對團隊內(nèi)開發(fā)人員的信任,外加自動化測試套件來維護代碼質(zhì)量,而PR開發(fā)模式的出發(fā)點是對陌生開發(fā)人員的不信任,從而需要依賴于人工審核來維護代碼質(zhì)量。
基于不同的出發(fā)點,我們也常常能看見二者被使用到不同的項目之中。開源項目和商業(yè)項目則是非常典型的例子,不同的項目背景往往也有著不同的組織結(jié)構(gòu)。
開源項目通常是由一些受信任核心成員和不受信任的陌生的開發(fā)人員組成。任何陌生人都能接觸到開源項目的源碼,并且通過創(chuàng)建分支,提交PR的形式,對項目進行貢獻(xiàn)。而這些工作,在提交PR之前,項目中的核心成員,對這個人和這個PR完成所需要的時間都是不確定或者說無感知的。PR開發(fā)模式很好地解耦了mainline與不受信任開發(fā)者之間的依賴關(guān)系,既不影響現(xiàn)有軟件的狀態(tài),同時又為想要對軟件作出貢獻(xiàn)的人提供了較低的門檻,并且保證了核心成員對軟件的質(zhì)量的充分話語權(quán)。
商業(yè)項目則不同,其通常是由技術(shù)領(lǐng)導(dǎo)人和一些開發(fā)人員組成。技術(shù)領(lǐng)導(dǎo)人對團隊中的開發(fā)人員會有一定的了解,并且對一個功能大概會在什么時間內(nèi)完成也會有相應(yīng)的計劃,整個團隊通常具有一定的信任度基礎(chǔ)。
因此,PR開發(fā)模式常見于開源項目中,而mainline開發(fā)模式則常見于商業(yè)項目中。
在受信任的團隊氛圍中工作通常既有利于團隊發(fā)展,對個人也是一種鼓勵。因此,mainline開發(fā)模式對開發(fā)人員更為友好。由于團隊信任度并非一成不變,因此在某些時候,使用PR開發(fā)模式也不失為一種好方法。
以我個人的經(jīng)歷舉例:在我剛開始參加工作,成為一名開發(fā)人員的時候,當(dāng)時項目上正好使用PR開發(fā)模式,我會經(jīng)常去找經(jīng)驗豐富的前輩幫忙看自己的PR。不同于code review,PR往往給予代碼審核者一個更加全面的視角,因為不限于code review的時間,審核人員也能根據(jù)自己的思路對整個PR的實現(xiàn)代碼進行詳細(xì)的理解,因此,PR的審核相較于code review會更加仔細(xì),對于代碼質(zhì)量有更加嚴(yán)格的控制;而對于被審核者,根據(jù)相應(yīng)的指導(dǎo)和建議進行代碼修改,也能較快地提升代碼的能力。但在加入項目一年多以后,大家對彼此更加了解和信任之后,則采取了mainline開發(fā)模式和code review的相對較寬松的方式來對代碼質(zhì)量進行管理。
結(jié)語
盡管在當(dāng)下隨著遠(yuǎn)距離辦公更為普遍,PR開發(fā)模式越來越流行,我們?nèi)匀恍枰⌒闹?jǐn)慎地使用它。在使用PR開發(fā)模式的時候,我們需要合理決定PR的大小,避免PR過大導(dǎo)致將codeline演變成feature branch。同時,團隊需要提出合理的機制以保證PR審核的及時性,否則這種開發(fā)模式將極大地阻礙團隊的開發(fā)效率。
相比之下,我個人更傾向于mainline開發(fā)模式,它在團隊交流上具有天然的優(yōu)勢。開發(fā)人員之間其實是通過集成來進行交流的。怎么快速知道別人的代碼更改會不會影響我,怎么快速知道我的代碼對mainline產(chǎn)生的影響,怎么將自己的代碼放到大家可見的地方以讓別人知道我的更改。這一系列的問題的根本解決方式就是:與mainline快速集成。其次,關(guān)于集成的設(shè)計成本也較PR開發(fā)模式的一個PR(active development line)對應(yīng)一條CI更低。最后,在談到團隊信任氛圍上,mainline開發(fā)模式的出發(fā)點是以營造一個相互信任的開發(fā)環(huán)境,并同時賦予每一個開發(fā)人員自由向mainline提交代碼的權(quán)利,這是一種更加健康和可持續(xù)發(fā)展的方式。


























