谷歌為什么把上十億行代碼都放在一個(gè)倉庫里
相對于一般公司,Google 使用了單一代碼倉庫,很多人不理解為什么這么做。本文作者是谷歌基礎(chǔ)設(shè)施小組的工程師,他對這個(gè)問題進(jìn)行了詳細(xì)解讀。
隨著 Google 開發(fā)軟件數(shù)量穩(wěn)步增加,Google 代碼庫的規(guī)模也呈指數(shù)增長。 因此,用于管理代碼庫的技術(shù)也發(fā)生了顯著變化。
本文概述了該代碼庫的規(guī)模,并詳細(xì)介紹了 Google 定制的集中式代碼庫以及該模型的選擇原因。Google 使用自主開發(fā)的版本控制系統(tǒng),管理公司的代碼庫,這個(gè)集中式系統(tǒng)是許多 Google 開發(fā)人員工作流程的基礎(chǔ)。在這里,我們提供了系統(tǒng)和工作流的背景,這些系統(tǒng)和工作流程可以有效地管理和高效地使用這樣一個(gè)大型代碼庫。我們將解釋 Google 的“基于 trunk 的開發(fā)”策略和支持系統(tǒng),以及構(gòu)建工作流程,還有保持 Google 的代碼庫健康的工具,包括用于靜態(tài)分析,代碼清理和簡化 code review 的軟件。
Google 規(guī)模
Google 95%的軟件開發(fā)人員使用的代碼庫滿足超大規(guī)模系統(tǒng)的定義[4],該倉庫是可以成功擴(kuò)展集中式代碼庫的證據(jù)。
Google 代碼庫包含大約十億個(gè)文件,并且具有約 3500 萬次提交的歷史(包含 Google 18 年所有的代碼提交)。該代碼庫包含 86TB 的數(shù)據(jù),包括 900 萬個(gè)源文件以及大約 20 億行代碼。
文件總數(shù)還包括復(fù)制到發(fā)布分支的源文件、最新版本刪除的文件、配置文件、文檔和數(shù)據(jù)文件。請參閱此處的表格,以了解 2015 年 1 月以來 Google 存儲(chǔ)庫統(tǒng)計(jì)信息的摘要。
2014 年,每周在 Google 代碼庫中有 1500 萬行代碼被修改。相比之下,Linux 內(nèi)核是一個(gè)大型開源軟件代碼庫示例,該代碼庫包含的 40,000 個(gè)文件中共有大約 1500 萬行代碼。[14]
Google 的代碼庫由來自世界各國數(shù)十個(gè)辦事處的 25,000 多名 Google 軟件開發(fā)人員共享。在典型的工作日,他們通常會(huì)對代碼庫進(jìn)行 16,000 次更改,另有 24,000 次更改由自動(dòng)化系統(tǒng)提交。
每天,代碼庫提供數(shù)十億次文件讀取請求,峰值每秒大約有 80 萬個(gè)查詢,工作日平均每秒大約有 50 萬個(gè)查詢,大部分流量來自 Google 的分布式構(gòu)建和測試系統(tǒng)。
總提交的代碼包括交互式用例或用戶數(shù)據(jù)以及自動(dòng)化提交的代碼,假期(如圣誕節(jié)和元旦,美國感恩節(jié)和美國獨(dú)立日)會(huì)有大幅度提交行數(shù)下跌。
2012 年 10 月,Google 代碼庫增加了對 Windows 和 Mac 用戶的支持(之前僅支持Linux),現(xiàn)有的 Windows 和 Mac 代碼庫與主代碼庫合并,Google 的代碼庫合并工具將所有歷史變更歸因于其原始作者。
根據(jù)每周提交的圖表顯示,到2012年之前,提交率由用戶主導(dǎo),此時(shí) Google 將代碼庫改為私有實(shí)現(xiàn)。如下所述,在此之后,自動(dòng)提交到存儲(chǔ)庫的代碼開始增加,代碼提交的增長主要是由于自動(dòng)化。
管理這種規(guī)模的代碼庫和開發(fā)對于 Google 來說是一個(gè)持續(xù)的挑戰(zhàn),盡管經(jīng)過幾年的試驗(yàn),Google 還沒有找到一個(gè)商業(yè)上可用的或開放源代碼版本控制系統(tǒng),以便在單一代碼庫中支持這種規(guī)模,Google 解決此問題的專有系統(tǒng)是 Piper。
背景
你在審視使用單一代碼庫的優(yōu)缺點(diǎn)之前,需要了解一些 Google 工具和工作流的背景。
Piper and CitC
Piper 是一個(gè)大型代碼庫,在標(biāo)準(zhǔn)的 Google 基礎(chǔ)設(shè)施上實(shí)現(xiàn),最初是基于 BigTable,現(xiàn)在是基于Spanner。[3] Piper 分布在全球 10 個(gè) Google 數(shù)據(jù)中心,依靠 Paxos [6]算法來保證副本一致性。
該架構(gòu)提供了高冗余,并有助于優(yōu)化 Google 軟件開發(fā)人員的延遲。此外,緩存和異步操作可以隱藏大量網(wǎng)絡(luò)延遲。這很重要,因?yàn)楂@得 Google 云工具鏈的全部優(yōu)勢需要開發(fā)人員在線。
在推出 Piper 之前,Google 主要依靠一臺(tái) Perforce 實(shí)例(加上自定義緩存基礎(chǔ)架構(gòu)[1],提供服務(wù)超過 10 年)。繼續(xù)擴(kuò)展 Google 代碼庫是開發(fā) Piper 的主要?jiǎng)恿Α?/p>
由于 Google 的源代碼是公司最重要的資產(chǎn)之一,因此安全功能是 Piper 設(shè)計(jì)的關(guān)鍵考慮因素。Piper 支持文件級(jí)訪問控制列表,所有Piper用戶都可以看到大部分代碼庫,也可以更嚴(yán)格地控制重要的配置文件或關(guān)鍵算法的文件。
可以對 Piper 中的文件進(jìn)行讀寫訪問。如果敏感數(shù)據(jù)文件被意外地提交給 Piper,則可以清除該文件,讀取日志允許管理員確定是否有人在刪除問題文件之前訪問過該文件。
在 Piper 工作流程中,開發(fā)人員在更改代碼庫之前創(chuàng)建文件的本地副本,這些文件存儲(chǔ)在開發(fā)人員擁有的工作區(qū)中。
Piper 工作區(qū)與 Apache Subversion(Git 中的本地克隆)或 Perforce 中的客戶端的工作副本相當(dāng),Piper 代碼庫中的更新可以根據(jù)需要被拉入工作空間并與正在進(jìn)行的工作進(jìn)行合并。
可以與其他開發(fā)人員共享工作空間快照以供審核,工作空間中的文件僅在經(jīng)過 Google code review 過程后才會(huì)提交到中央代碼庫。
大多數(shù)開發(fā)人員通過名為 Clients in Cloud 的系統(tǒng)或 CitC 訪問 Piper,該系統(tǒng)由基于云的存儲(chǔ)后端和 Linux FUSE [13] 文件系統(tǒng)組成,開發(fā)人員將他們的工作空間看作是文件系統(tǒng)中的目錄,將更改覆蓋在完整的Piper庫之上。
CitC 支持代碼瀏覽和 Unix 工具,無需本地克隆或同步狀態(tài)。開發(fā)人員可以在 Piper 存儲(chǔ)庫中的任何地方瀏覽和編輯文件,只有修改的文件才存儲(chǔ)在其工作空間中。
這種結(jié)構(gòu)意味著 CitC 工作區(qū)通常僅消耗少量存儲(chǔ)(平均工作空間少于 10 個(gè)文件),同時(shí)向開發(fā)人員呈現(xiàn)整個(gè) Piper 代碼庫。
對文件的所有寫入都作為快照存儲(chǔ)在 CitC 中,使得可以根據(jù)需要恢復(fù)以前的工作階段,可以明確命名,恢復(fù)或標(biāo)記快照以供審核。
CitC 工作區(qū)可以在任何連接到云的機(jī)器上使用,從而輕松切換機(jī)器并且不間斷地工作,這也使得開發(fā)人員可以在 CitC 工作區(qū)中查看彼此的工作,將所有正在進(jìn)行中的工作存儲(chǔ)在云中是 Google 工作流程的重要組成部分。
工作狀態(tài)可用于其他工具,包括基于云的構(gòu)建系統(tǒng),自動(dòng)測試基礎(chǔ)架構(gòu)以及代碼瀏覽,編輯和查看工具。
有幾個(gè)工作流程利用了 CitC 中未提交代碼的特性,使軟件開發(fā)人員能夠更有效率的使用大型代碼庫。
例如,當(dāng)發(fā)送更改 code review 時(shí),開發(fā)人員可以啟用自動(dòng)提交選項(xiàng),這在代碼作者和審閱者處于不同的時(shí)區(qū)時(shí)特別有用。review 被標(biāo)記為完成時(shí),測試將會(huì)運(yùn)行。
如果可以通過測試,代碼將被合并到代碼庫,不需要進(jìn)一步的人工干預(yù),Google 代碼瀏覽工具 CodeSearch 支持使用 CitC 工作區(qū)進(jìn)行簡單的編輯。
瀏覽資料庫時(shí),開發(fā)人員可以點(diǎn)擊按鈕進(jìn)入編輯模式,并進(jìn)行簡單的更改(例如修改打字或改進(jìn)評(píng)論)。然后,在不離開代碼瀏覽器的情況下,他們可以將自己的更改發(fā)送到適當(dāng)?shù)膶忛喺撸⒂米詣?dòng)提交。
Piper 也可以在沒有 CitC 的情況下使用,開發(fā)人員可以將 Piper 工作區(qū)存儲(chǔ)在本地計(jì)算機(jī)上,Piper 還可以和 Git 互操作。目前,超過 80% 的 Piper 用戶使用 CitC,由于 CitC 有許多優(yōu)勢,使用率持續(xù)增長。
Piper 和 CitC 可以保證在 Google 代碼庫的規(guī)模下,使用單一代碼庫進(jìn)行有效的工作。這些系統(tǒng)的設(shè)計(jì)和架構(gòu)都受到 Google 采用的基于 trunk 的開發(fā)模式的影響,如下所述。
基于 trunk 的開發(fā)
Google 在 Piper 源代碼庫之上實(shí)施基于 trunk 的開發(fā)。Piper 用戶絕大多數(shù)在“head”或最新版本的“trunk”或“mainline”代碼副本中工作,對代碼庫的更改是串行的。
基于 trunk 的開發(fā)與中央代碼庫的組合定義了單一代碼庫模型,在任何提交之后,其他所有開發(fā)人員都能看到更改。Piper 用戶對 Google 代碼庫的一致視圖是提供本文后面描述的優(yōu)勢的關(guān)鍵。
基于 trunk 的開發(fā)是有益的,因?yàn)樗苊饬撕喜㈤L支鏈分支時(shí)的痛苦。盡管代碼分支通常用于發(fā)布上線,但是在 Google 代碼分支支持的不好。
通常在 trunk 上開發(fā) bug fix 和必須添加到版本中的增強(qiáng)功能,然后將其引入到 release 分支中.
由于需要保持穩(wěn)定性并限制發(fā)布分支上的流失,所以 release 通常是“head”的快照,根據(jù)需要從”head”拉出可選的少量帶代碼,在 branch 和 trunk 上并行開發(fā)的長壽命 branch 是非常罕見的。
Piper 和 CitC 可以在 Google 代碼庫的規(guī)模下,使用單一源代碼庫進(jìn)行有效的工作。
當(dāng)開發(fā)新功能時(shí),新舊代碼路徑通常同時(shí)存在,通過使用條件標(biāo)志來控制。這種技術(shù)避免了開發(fā)分支的需要,并且通過配置更新來打開或者關(guān)閉功能。
雖然開發(fā)人員還需要一些額外的復(fù)雜性,但是避免了開發(fā)分支合并問題,標(biāo)志翻轉(zhuǎn)使得用戶切換具有問題的新實(shí)現(xiàn)變得更加容易和快捷。
該方法通常用于項(xiàng)目特定的代碼,而不是通用的庫代碼,最終會(huì)刪除標(biāo)志和舊代碼。Google 使用類似的方法來對不同代碼做測試,這樣的 A / B test 可以從代碼性能得到與產(chǎn)品變化相關(guān)的參數(shù)。
Google 工作流程
需要幾種最佳實(shí)踐和支持系統(tǒng),以避免在基于 trunk 的開發(fā)模式中碰到的問題。例如,Google 有一個(gè)自動(dòng)測試基礎(chǔ)設(shè)施,可以在幾乎每個(gè)提交上啟動(dòng)所有受影響的依賴項(xiàng)測試。
如果一次代碼更改造成構(gòu)建破壞,系統(tǒng)就會(huì)自動(dòng)撤消更改。為了減少發(fā)生的錯(cuò)誤代碼的發(fā)生率,高度可定制的 Google “預(yù)提交”基礎(chǔ)架構(gòu)可以在更改代碼添加到代碼庫之前自動(dòng)進(jìn)行測試和分析。
針對所有更改運(yùn)行一組全局預(yù)先提交分析,代碼所有者可以創(chuàng)建僅在其指定的代碼庫中的目錄上運(yùn)行的自定義分析,僅有一小部分非常低級(jí)別的核心庫使用 branch 的機(jī)制,以保證在新版本暴露給客戶端代碼之前執(zhí)行其他測試。
鼓勵(lì)代碼質(zhì)量的一個(gè)重要方面是期望在提交到代碼庫之前對所有代碼進(jìn)行 review。大多數(shù)開發(fā)人員可以在代碼庫的任何地方查看和建議更改(除了一組更加精心控制的高度機(jī)密代碼之外)。
不熟悉的開發(fā)人員更改相關(guān)代碼的風(fēng)險(xiǎn)通過代碼 review 過程和代碼所有權(quán)的概念得到緩解,Google 代碼庫以樹結(jié)構(gòu)布局,每個(gè)目錄都有一組所有者控制是否接受目錄中文件的更改。
所有者通常是在相關(guān)目錄中處理項(xiàng)目的開發(fā)人員,變更通常會(huì)從一位開發(fā)人員收到詳細(xì)的代碼審查開始,從而評(píng)估變更的質(zhì)量,以及所有者的認(rèn)可批準(zhǔn),評(píng)估變更對的適用性。
代碼 review 者會(huì)對代碼質(zhì)量方面進(jìn)行評(píng)論,包括設(shè)計(jì),功能,復(fù)雜性,測試,命名,評(píng)論質(zhì)量和代碼風(fēng)格。
Google 已經(jīng)編寫了一個(gè)名為 Critique 的代碼審查工具,允許審閱者查看代碼的演變,并對任何一行的更改進(jìn)行評(píng)論。它鼓勵(lì)進(jìn)一步的修改和 review,以達(dá)到所有者的要求。
Google 的靜態(tài)分析系統(tǒng)(Tricorder [10])和預(yù)提交基礎(chǔ)設(shè)施還可以在 Google 代碼審查工具中自動(dòng)提供有關(guān)代碼質(zhì)量,測試覆蓋率和測試結(jié)果的數(shù)據(jù)。這些計(jì)算密集型檢查被定期觸發(fā),發(fā)送代碼修改以供 review。
Tricorder 還為許多錯(cuò)誤提供了修改的建議,這些系統(tǒng)提供重要數(shù)據(jù),以提高代碼審查的有效性,并保持 Google 代碼庫的健康。
Google 開發(fā)人員小組不時(shí)進(jìn)行代碼清理,以進(jìn)一步維護(hù)代碼庫的健康。執(zhí)行這些更改的開發(fā)人員通常將過程分為兩個(gè)階段。
首先進(jìn)行大的向后兼容的更改,一旦完成,可以進(jìn)行第二個(gè)較小的更改以刪除不再引用的代碼,Rosie 工具支持這種大規(guī)模清理和代碼更改的第一階段。
使用 Rosie,開發(fā)人員可以創(chuàng)建一個(gè)大補(bǔ)丁。Rosie負(fù)責(zé)將大補(bǔ)丁分成較小的補(bǔ)丁,獨(dú)立測試,發(fā)送出去進(jìn)行代碼 review,并在通過測試和代碼審查后自動(dòng)提交。
Rosie 根據(jù)項(xiàng)目目錄行拆分補(bǔ)丁,依靠前面描述的代碼所有權(quán)層次結(jié)構(gòu)將補(bǔ)丁發(fā)送給適當(dāng)?shù)膶忛喺摺?/p>
隨著 Rosie 的流行度和使用率的增長,顯而易見,必須建立一些控制措施,以將 Rosie 的用途限制高價(jià)值變化中。
2013 年,Google 通過了正式的大規(guī)模變化 review 流程,導(dǎo)致了從 2013 年到 2014 年的 Rosie 數(shù)量的減少。在評(píng)估 Rosie 變更時(shí),評(píng)審委員會(huì)將變更的收益與審閱者時(shí)間和存儲(chǔ)庫流失的成本相平衡,我們稍后更仔細(xì)地研究類似的權(quán)衡。
總而言之,Google 開發(fā)了許多工具來支持其龐大的代碼庫,包括基于 trunk 的開發(fā),分布式源代碼存儲(chǔ)庫 Piper,工作區(qū)客戶端 CitC 以及工作流支持工具 Critique,CodeSearch,Tricorder,和 Rosie,我們在這里討論這個(gè)模型的利弊。
分析
本節(jié)概述并擴(kuò)展了單一代碼庫的優(yōu)勢以及與維護(hù)此類模型規(guī)模相關(guān)的成本。
優(yōu)點(diǎn)
支持超大規(guī)模的 Google 代碼庫,同時(shí)為成千上萬的用戶服務(wù),保持良好的性能是一個(gè)挑戰(zhàn),但由于其引人注目的優(yōu)勢,Google 已經(jīng)擁抱了單一代碼庫。
最重要的是它支持:
- 統(tǒng)一版本。
- 廣泛的代碼共享和重用。
- 簡化依賴關(guān)系管理。
- 原子變化。
- 大規(guī)模重構(gòu)。
- 團(tuán)隊(duì)合作。
- 靈活的團(tuán)隊(duì)邊界和代碼所有權(quán)。
- 代碼可見性和清晰的樹結(jié)構(gòu),提供隱含的團(tuán)隊(duì)命名空間。
單一代碼庫提供統(tǒng)一的版本控制和單一代碼來源。對于哪個(gè)存儲(chǔ)庫托管文件的權(quán)威版本,并不存在任何混淆。
如果一個(gè)團(tuán)隊(duì)想要依賴另一個(gè)團(tuán)隊(duì)的代碼,可以直接依賴,Google代碼庫包含大量有用的庫,而單一代碼庫可以引導(dǎo)廣泛的代碼共享和重用。
Google 構(gòu)建系統(tǒng)[5]可以輕松地在目錄之間包含代碼,從而簡化依賴關(guān)系管理。對項(xiàng)目的依賴性的更改會(huì)觸發(fā)依賴代碼的重建,由于所有代碼都在相同的存儲(chǔ)庫中進(jìn)行版本控制,所以只有一個(gè)版本,也不關(guān)心依賴關(guān)系的獨(dú)立版本。
最值得注意的是,該模型允許 Google 避免當(dāng) A 依賴于 B 和 C 時(shí)發(fā)生的“鉆石依賴”問題,B 和 C 都依賴于 D,但 B 需要版本 D.1 和 C 需要版本 D 0.2。
在大多數(shù)情況下,可能很難在不導(dǎo)致破壞的情況下發(fā)布新版本,因?yàn)樗姓{(diào)用方必須同時(shí)更新,當(dāng)庫調(diào)用者托管在不同的存儲(chǔ)庫中時(shí),這種更新很困難。
在開源世界中,依賴關(guān)系通常被庫更新所破壞,查找所有共同工作的依賴庫版本都是一個(gè)挑戰(zhàn)。更新依賴關(guān)系的版本對于開發(fā)人員來說可能是痛苦的,延遲更新可能會(huì)變成非常昂貴的技術(shù)債務(wù)。
使用單一代碼庫,對于更新庫的人來說,在同一時(shí)間更新所有受影響的依賴關(guān)系更容易。依賴引起的技術(shù)性債務(wù)在作出變更時(shí)立即予以償還,基礎(chǔ)庫的更改將立即通過依賴關(guān)系鏈傳播到依賴于庫的最終產(chǎn)品中,而不需要單獨(dú)的同步或遷移步驟。
請注意,如下所述,在源/ API 級(jí)別以及二進(jìn)制文件之間可能存在鉆石依賴問題。[12]在谷歌,通過使用靜態(tài)鏈接避免了二進(jìn)制問題。
進(jìn)行原子變化的能力也是整體模型的一個(gè)非常強(qiáng)大的特征,開發(fā)人員可以在一致的操作中,對代碼庫中的數(shù)百或數(shù)千個(gè)文件進(jìn)行重大變更。例如,開發(fā)人員可以在單個(gè)提交中重命名類或函數(shù),但不會(huì)破壞任何構(gòu)建或測試。
在單一代碼庫中,或至少在集中式服務(wù)器上,所有源代碼的可用性使得核心庫的維護(hù)者在提交高影響力更改之前可以更輕松地執(zhí)行測試和性能基準(zhǔn)測試。
這種方法對于探索和測量高度破壞性變化的價(jià)值是有用的, 一個(gè)具體的例子是評(píng)估轉(zhuǎn)換 Google 數(shù)據(jù)中心以支持非 x86 機(jī)器架構(gòu)的可行性的實(shí)驗(yàn)。
由于 Google 代碼庫的結(jié)構(gòu),開發(fā)人員無需決定代碼庫邊界,工程師不需要“branch”共享庫的開發(fā),或者跨倉庫合并來更新代碼。
團(tuán)隊(duì)邊界是流動(dòng)的,當(dāng)項(xiàng)目所有權(quán)更改或計(jì)劃合并系統(tǒng)時(shí),所有代碼都已在同一個(gè)庫中。這種環(huán)境使代碼庫的循環(huán)重構(gòu)和重組變得容易,移動(dòng)項(xiàng)目和更新依賴關(guān)系可以原子地應(yīng)用于代碼庫,并且受影響代碼的開發(fā)歷史保持不變且可用。
單一代碼庫的另一個(gè)屬性是容易理解的代碼庫的布局,因?yàn)樗唤M織在單個(gè)樹中,每個(gè)團(tuán)隊(duì)在主樹中都有一個(gè)目錄結(jié)構(gòu),有效地充當(dāng)項(xiàng)目自己的命名空間。
每個(gè)源文件都可以通過單個(gè)字符串唯一標(biāo)識(shí),該文件路徑可選地包含修訂版本號(hào),瀏覽代碼庫,很容易了解任何源文件如何適用于代碼庫。
Google 代碼庫不斷發(fā)展,更復(fù)雜的代碼庫現(xiàn)代化工作(例如將其更新為 C++ 11 或推出性能優(yōu)化[9])通常由專用的代碼庫維護(hù)者集中管理。
這樣的努力可以觸及五十萬個(gè)變量聲明或函數(shù)調(diào)用點(diǎn)(分布在數(shù)十萬個(gè)源代碼文件中),由于所有項(xiàng)目都集中存儲(chǔ),所以專家團(tuán)隊(duì)可以為整個(gè)公司做這項(xiàng)工作,而不是要求很多人開發(fā)自己的工具。
舉個(gè)例子
請考慮 Google 的編譯器團(tuán)隊(duì),他們會(huì)確保 Google 的開發(fā)人員使用最新的工具鏈,并從生成的代碼和“可調(diào)試性”的最新改進(jìn)中獲益。
單一代碼庫使編譯團(tuán)隊(duì)能夠全面了解 Google 如何使用各種語言,并允許他們進(jìn)行代碼庫范圍的清理,以防止更改破壞構(gòu)建。
這大大簡化了編譯器驗(yàn)證,從而減少了編譯器發(fā)布周期,并使 Google 有可能安全地執(zhí)行編譯器版本(通常每年對 C ++ 編譯器來說超過 20 個(gè))升級(jí)。
通過對夜間運(yùn)行性能測試和回歸測試產(chǎn)生的數(shù)據(jù)進(jìn)行分析,編譯器團(tuán)隊(duì)可以將默認(rèn)編譯器設(shè)置調(diào)整為最佳。
例如,谷歌的 Java 開發(fā)人員都看到他們的垃圾回收(GC) CPU 消耗量下降了50%以上,而且 GC 停留時(shí)間從 2014 年到 2015 年下降了 10%-40%。另外,當(dāng)軟件發(fā)現(xiàn)錯(cuò)誤,編譯器團(tuán)隊(duì)有可能添加新的警告以防止錯(cuò)誤重復(fù)發(fā)生。
結(jié)合此更改,他們會(huì)掃描整個(gè)存儲(chǔ)庫以查找并修復(fù)正在存在該問題的其他實(shí)例,然后再轉(zhuǎn)到新的編譯器錯(cuò)誤,過去的實(shí)踐證明編譯器拒絕有問題的代碼大大提升了 Google 的代碼運(yùn)行狀況。
將所有源代碼存儲(chǔ)在通用版本控制存儲(chǔ)庫中可以使代碼庫維護(hù)者有效地分析和更改 Google 的源代碼。像 Refaster [11] 和 ClangMR [15] (通常與 Rosie 一起使用)這樣的工具利用 Google 源代碼的單一視圖來執(zhí)行源代碼的高級(jí)轉(zhuǎn)換。
單一代碼庫捕獲所有依賴關(guān)系信息,可以放心地刪除舊的 API。因?yàn)榭梢允顾姓{(diào)用者使用新API,在任何給定時(shí)間,通過確保更改的原子性和整個(gè)存儲(chǔ)庫的單一全局視圖,單一代碼庫極大地簡化了這些工具的開發(fā)過程。
鼓勵(lì)代碼質(zhì)量的 Google 文化其中一個(gè)重要方面是期望在提交到代碼庫之前對所有代碼進(jìn)行審核。
成本和權(quán)衡
注意單一代碼庫絕不意味著整體化的軟件設(shè)計(jì),使用這個(gè)模型涉及必須考慮一些缺點(diǎn)和權(quán)衡。
這些成本和權(quán)衡分為三類:
- 開發(fā)和執(zhí)行的工具投資。
- 代碼庫復(fù)雜性,包括不必要的依賴性和代碼發(fā)現(xiàn)的困難。
- 達(dá)到代碼健壯性的努力。
在許多方面,單一代碼庫導(dǎo)致更簡單的工具。然而,還需要將工具規(guī)模擴(kuò)展到代碼庫的規(guī)模。
例如,Google 已經(jīng)為 Eclipse 集成開發(fā)環(huán)境(IDE)編寫了一個(gè)自定義插件,以使 IDE 能夠使用大型代碼庫。
Google 的代碼索引系統(tǒng)支持靜態(tài)分析,代碼瀏覽工具中的交叉引用,以及 Emacs,Vim 和其他開發(fā)環(huán)境的豐富的 IDE 功能,這些工具需要持續(xù)的投資來管理日益增長的 Google 代碼庫規(guī)模。
除了建立和維護(hù)可擴(kuò)展工具的投資外,Google 還必須承擔(dān)運(yùn)行這些系統(tǒng)的成本,其中一些是非常計(jì)算密集型的。
許多 Google 的內(nèi)部開發(fā)人員工具套件,包括自動(dòng)化測試基礎(chǔ)架構(gòu)和高度可擴(kuò)展的構(gòu)建基礎(chǔ)設(shè)施,對于支持單一代碼庫的規(guī)模至關(guān)重要。因此,必須權(quán)衡如何運(yùn)行這些工具以平衡執(zhí)行成本與提供給開發(fā)人員的數(shù)據(jù)的好處。
單一代碼庫更容易理解代碼庫的結(jié)構(gòu),因?yàn)樵谝蕾囮P(guān)系之間沒有跨倉庫邊界。然而,隨著規(guī)模的增加,代碼查找變得更加困難,因?yàn)橄駁rep這樣的標(biāo)準(zhǔn)工具基本不可用。
開發(fā)人員必須能夠探索代碼庫,找到相關(guān)的庫,并了解如何使用它們以及誰編寫它們,庫作者經(jīng)常需要了解他們的 API 如何被使用。
這需要對代碼搜索和瀏覽工具的重大投資,Google 已經(jīng)發(fā)現(xiàn)這種投資非常有益,提高了所有開發(fā)人員的生產(chǎn)力。[9]
訪問整個(gè)代碼庫鼓勵(lì)廣泛的代碼共享和重用,有些人會(huì)認(rèn)為,這種模式依賴于 Google 構(gòu)建系統(tǒng)的可擴(kuò)展性,使得添加依賴關(guān)系變得太容易,并且減少了軟件開發(fā)人員設(shè)計(jì)穩(wěn)定且精心設(shè)計(jì)的 API 的動(dòng)機(jī)。
由于創(chuàng)建依賴關(guān)系的輕松,通常團(tuán)隊(duì)不要考慮其依賴關(guān)系圖,使代碼清理更容易出錯(cuò)。不必要的依賴可能會(huì)增加項(xiàng)目對下游構(gòu)建破壞的風(fēng)險(xiǎn),導(dǎo)致二進(jìn)制文件膨脹,并在構(gòu)建和測試中創(chuàng)造額外的工作,此外,維護(hù)遺留項(xiàng)目會(huì)導(dǎo)致生產(chǎn)力下降。
Google 的試圖控制不必要的依賴,已經(jīng)有工具幫助識(shí)別和刪除不需要依賴關(guān)系。還存在用于識(shí)別未充分利用的依賴關(guān)系或識(shí)別不需要的庫的工具。
工具 Clipper 依賴于一個(gè)自定義的 Java 編譯器來生成一個(gè)精確的交叉引用索引,然后,它使用索引構(gòu)建可達(dá)性圖,并確定從不使用什么類。
Clipper 可以通過幫助開發(fā)人員找到相對容易刪除或分解的目標(biāo)來指導(dǎo)依賴重構(gòu)的工作。、
開發(fā)人員可以在一個(gè)一致的操作中,通過存儲(chǔ)庫中的數(shù)百或數(shù)千個(gè)文件進(jìn)行重大變更。
依賴重構(gòu)和清理工具是有幫助的,但理想情況下,代碼所有者應(yīng)該能夠防止創(chuàng)建不必要的依賴關(guān)系。
2011 年,Google 開始推廣 API 可見性的概念,將新 API 的默認(rèn)可見性設(shè)置為“私有”,這迫使開發(fā)人員明確地標(biāo)記 API,以供其他團(tuán)隊(duì)使用。從 Google 的大型代碼庫的經(jīng)驗(yàn)中學(xué)到的教訓(xùn)應(yīng)該是盡快實(shí)施,以鼓勵(lì)更好的依賴結(jié)構(gòu)。
大多數(shù) Google 代碼可供所有 Google 開發(fā)人員使用,這導(dǎo)致了一種文化,一些團(tuán)隊(duì)希望其他開發(fā)人員閱讀他們的代碼,而不是為他們提供單獨(dú)的 API 文檔。
這種做法有利與弊,開發(fā)人員有時(shí)會(huì)閱讀 API 代碼,最終依賴于底層的實(shí)現(xiàn)細(xì)節(jié),這種行為可能會(huì)為那些不愿意向用戶暴露的細(xì)節(jié)的團(tuán)隊(duì)提供一些維護(hù)負(fù)擔(dān)。
該模型還要求團(tuán)隊(duì)在使用開源代碼時(shí)相互協(xié)作,存代碼的一個(gè)區(qū)域保留用于開源代碼(在 Google 開發(fā)或外部開發(fā))。
為了防止依賴沖突,需要確保在任何給定的時(shí)間只有一個(gè)開源版本可用,使用開源代碼的團(tuán)隊(duì)在進(jìn)行依賴升級(jí)時(shí),會(huì)花時(shí)間處理新版本的開源庫。
Google 投入巨大的努力來維護(hù)代碼健康,以解決與代碼庫復(fù)雜性和依賴關(guān)系管理相關(guān)的一些問題。
例如,專用工具會(huì)自動(dòng)檢測和刪除死碼,分割大量重構(gòu),并自動(dòng)分配代碼評(píng)估(如通過 Rosie),并將 API 標(biāo)記為不推薦使用。
需要人力運(yùn)行這些工具并管理相應(yīng)的大規(guī)模代碼更改,審查代碼庫范圍內(nèi)的清理和其他工作引起的持續(xù)簡單重構(gòu)也會(huì)產(chǎn)生成本。
備擇方案
隨著像 Git 這樣的分布式版本控制系統(tǒng)(DVCS)的普及和使用越來越多,Google 考慮是否將 Piper 轉(zhuǎn)移到 Git 作為其主要的版本控制系統(tǒng)。
Google 的一個(gè)團(tuán)隊(duì)專注于支持 Git,Google 在 Google 主代碼庫之外由 Google 的 Android 和 Chrome 團(tuán)隊(duì)使用,由于外部合作伙伴和開源協(xié)作,使用 Git 對于這些團(tuán)隊(duì)很重要。
Git 社區(qū)強(qiáng)烈建議開發(fā)人員擁有越來越多的代碼庫,Git-clone 操作需要將所有內(nèi)容復(fù)制到本地計(jì)算機(jī),這是與大型存儲(chǔ)庫不兼容的過程。要轉(zhuǎn)移到基于 Git 的源代碼托管,有必要將 Google 的存儲(chǔ)庫拆分成數(shù)千個(gè)獨(dú)立的存儲(chǔ)庫,以實(shí)現(xiàn)合理的性能。
這樣的重組將需要 Google 開發(fā)人員的文化和工作流程更改。作為比較,Google 的 Git 托管的 Android 代碼分為超過 800 個(gè)獨(dú)立的代碼庫。
鑒于 Google 已經(jīng)建立的現(xiàn)有工具所獲得的價(jià)值以及整體代碼庫結(jié)構(gòu)的許多優(yōu)勢,轉(zhuǎn)換到越來越多的代碼庫對于 Google 的主代碼庫來說是沒有意義的,移動(dòng)到 Git 或需要代碼庫拆分對 Google 來說并不引人注目。
Google 源代碼團(tuán)隊(duì)目前的投資主要集中在內(nèi)部源代碼系統(tǒng)的持續(xù)可靠性,可擴(kuò)展性和安全性上,該團(tuán)隊(duì)還在與Mercurial進(jìn)行實(shí)驗(yàn)性工作,這是一款類似 Git 的開源DVCS。
目標(biāo)是向 Mercurial 客戶端添加可伸縮性功能,以便高效地支持 Google 的規(guī)模。這將為 Google 開發(fā)人員提供一種與單一代碼庫庫一起使用流行的 DVCS 風(fēng)格工作流的替代方案。
這一努力與開源的 Mercurial 社區(qū)合作,其中包括來自其他公司的貢獻(xiàn)者。
結(jié)論
Google 在 1999 年將現(xiàn)有的 Google 代碼庫從 CVS 遷移到 Perforce 時(shí),選擇了單一源代碼管理策略,早期的 Google 工程師認(rèn)為,單獨(dú)的代碼庫比多個(gè)代碼庫要嚴(yán)格得多,盡管當(dāng)時(shí)他們沒有預(yù)料到代碼庫的未來規(guī)模以及所有支持的工具。
多年來,隨著繼續(xù)擴(kuò)大集中式存儲(chǔ)庫所需的投資增長,Google 領(lǐng)導(dǎo)層偶爾會(huì)考慮從單模模式轉(zhuǎn)變是否有意義。盡管需要努力,但由于其優(yōu)勢,Google 選擇堅(jiān)持使用集中式單一代碼庫。
源代碼管理的單一模型不適合所有人,它最適合像 Google 這樣的組織,具有開放和協(xié)作的文化。對于代碼庫的大部分是私有的或組之間隱藏的組織來說,這不太適用。
在 Google 方面,我們發(fā)現(xiàn)通過一些投資,源代碼管理的整體模式可以成功擴(kuò)展到具有超過十億個(gè)文件,3500 萬個(gè)提交和全球數(shù)千個(gè)開發(fā)者的代碼庫。
隨著 Google 和 Google 內(nèi)部項(xiàng)目的規(guī)模和復(fù)雜性不斷增長,我們希望本文中描述的分析和工作流程可以使他們對其代碼庫的長期結(jié)構(gòu)進(jìn)行權(quán)衡決策。


浙公網(wǎng)安備 33010602011771號(hào)