Google 是如何開發(fā) Web 框架的

作者:Filip Hracek

編譯:胡子大哈

翻譯原文:http://huziketang.com/blog/posts/detail?postId=58abfab1204d50674934c3a9

英文原文:How Google builds web frameworks

** 轉(zhuǎn)載請注明出處,保留原文鏈接以及作者信息**


眾所周知,Google 使用單一倉庫來共享所有代碼——20億行代碼,并且這個倉庫是采用基于 trunk 的開發(fā)方式。


(這毫無疑問是世界上最大的代碼倉庫。)

對于 Google 公司以外的開發(fā)者來說,這是令人驚訝的,也是反直覺的,但是它確實運作良好。(所給的鏈接 文章 中已經(jīng)給了很好的例子,這里就不累述了)

Google 的代碼庫在 Google 在全世界幾十個辦事處,共 25000 多名軟件工程師之間共享。在一個普通的工作日里,就有 16000 多次代碼提交。

這篇文章主要講述構(gòu)建一個開源 Web 框架(AngularDart)的開發(fā)過程。


(“Human users” 意味著谷歌的軟件工程師提交代碼)

只有一個版本

當(dāng)你在一個單倉庫項目中使用基于 trunk 的開發(fā)模式時,你的所有東西都只有一個版本。例如, Google 不會發(fā)生一個叫做 FooBar 的 App 使用 AngularDart 2.2.1 版本,而另一個叫做 BarFoo 的 App 使用 2.3.0 版本。所有的 App 都用同一個版本——而且是最新版本。


(基于 trunk 的開發(fā)模式時示意圖,來源:trunkbaseddevelopment.com

這就是為什么 Google 的工程師說所有的 Google 軟件都活在“失血邊緣”。

如果現(xiàn)在你的心里在大喊“太危險了!”,那就對了。在你的生產(chǎn)環(huán)境代碼里面依賴另外一個庫的 trunk(相當(dāng)于 git 里面的 master)分支上面的代碼聽起來確實很危險,但是你要知道,故事在這之前還有別的情節(jié)。

每次 commit 前的 74000 次測試

AngularDart 定義了 1601 個測試(這里)。但是當(dāng)你要提交一個改動到 Google 倉庫中的 AngularDart 源代碼時,所有依賴于 AngularDart 的測試都會被跑一遍。這時,大概有 74000 個測試(這依賴于你的改動有多大——系統(tǒng)會知道你影響了哪些測試,并且會啟發(fā)式地跳過這些測試)。

測試越多越好。

例如,我僅僅是做了一個小改動,用來在變化檢測插入驗證算法中做一個模擬某種場景的競爭情況(我添加了&& random.nectDouble() > .05這條 if 語句 中)。當(dāng)我運行它的時候(運行一次),AngularDart 本身的1601個測試并不需要跑,但是這個改動卻命中了一堆的客戶端測試。

這里面體現(xiàn)出真正的價值是,這些測試都是真實產(chǎn)品的測試。這些測試不僅數(shù)量龐大,同時它們也反映了開發(fā)者是如何使用的這個框架(不僅僅是框架的作者使用)。這樣做的意義是:框架的作者并不總是能夠正確地預(yù)估別人是怎么使用他的框架。

對正在生產(chǎn)環(huán)境運行的 App 也是有好處的,畢竟上面每個月有數(shù)十億美元的流水。開發(fā)者不是在一個業(yè)余時間拿來玩玩的 Demo App 上使用框架,而是一個成千上萬人投入在上面的線上產(chǎn)品使用。如果 Web 是和未來息息相關(guān)的,那么作為 Web 框架開發(fā)者,我們更應(yīng)該更好地支持后者的開發(fā)。

所以,如果一個框架導(dǎo)致依賴于它的一些 App 崩潰,會發(fā)生什么?

誰搞砸,誰修復(fù)

如果 AngularDart 的某位成員引入了一個引起崩潰的改動,那么他必須要為他們的用戶修復(fù)它。因為大家都使用單一的倉庫,因此 bug 很容易被發(fā)現(xiàn),并且可以立馬修復(fù)它。

因為 bug fix 的時候可能帶來新的 bug,因此 bug 和 fix 有可能是同時入代碼庫。當(dāng)然,在入庫之前它們都會被做代碼審查。

我們來給一個具體的例子。當(dāng) AngularDart 團隊里的某個成員修改了代碼影響了 AdWords 程序的運行,那么他們就要跑到 AdWords 去修復(fù)它們。在修復(fù)代碼過程中,他們會跑 AdWords 已有的測試,也可以新建測試。然后他們會把測試和 bug fix 都放入變更列表,并且申請代碼檢查。因為這個變更列表中包含了 AngularDart 代碼和 AdWords 的代碼,因此系統(tǒng)會自動地將代碼發(fā)送到兩個團隊進行審查,只有兩邊都通過了才能被提交入庫。

這是一個很好的防止閉門造車的方法。AngularDart 框架的開發(fā)者們可以訪問到數(shù)以百萬計行的代碼,這些代碼都依賴于該框架。他們不需要去假想其他人會怎么使用這個框架。(當(dāng)然他們只能訪問到 Google 的代碼,而訪問不到世界上其他依賴于 AngularDart 的代碼。)

別人用你的框架,但是你要升級別人的代碼,這樣聽起來會使得開發(fā)變慢。但是也沒想象中那么慢(可以看一下 AngularDart 10月份的進度),但是也確實讓開發(fā)進度慢了一些。這是一把雙刃劍,怎么來看待這個問題取決于你想要從這個框架中獲得什么。等下我們再來討論這個問題。

所以,如果有個 Google 的家伙跟你說,他們 Alpha 版本的庫已經(jīng)穩(wěn)定了并且可以應(yīng)用到生產(chǎn)環(huán)境了,你就知道是什么回事了。

大規(guī)模改動

那么,如果 AngularDart 要做一個大規(guī)模的變動(如版本從 2.x 升級到 3.0)并且會命中 74000 個測試該怎么辦呢?團隊要修復(fù)所有的問題嗎?難道他們要去更改上千個跟根本就不是他們寫的源文件嗎?

是的。

一個好的 類型安全系統(tǒng) 會使你的工作更有效率。例如,在一個類型安全的 Dart 中,類型安全可以保證所有變量都有一個確定的類型。那么你進行重構(gòu)的時候很多東西都可以做到自動化,而不需要開發(fā)者手工確認(rèn)。

當(dāng)類 Foo 中的一個方法bar()變成了baz(),你可以創(chuàng)建一個工具,它可以遍歷整個 Google 倉庫,找到所有 Foo 類的實例以及它子類的實例,并且把所有的bar()改成baz()。有了類型安全,你可以確定這個改動不會使任何地方崩潰。如果沒有類型安全,甚至這么一個簡單的修改都可能變得非常麻煩。


(根據(jù) Dart 代碼規(guī)范,一鍵格式化代碼)

另外一個對于大規(guī)模改動很有幫助的是 dart_style, Dart 的默認(rèn)格式器。所有 Google 的 Dart 代碼都是通過這個工具進行格式化的。在你的代碼被 push 到代碼審查人員之前,就通過 dart_style 進行格式化了,所以不存在像要不要把新的一行放在這或者放在那這樣的問題。這也被應(yīng)用到大規(guī)模的代碼重構(gòu)。

性能指標(biāo)

正如我上面所說的,AngularDart 得益于依賴于它的產(chǎn)品的測試。但僅僅只是測試還不夠,Google 對于 App 的性能要求的很嚴(yán)格,幾乎所有的產(chǎn)品都有基準(zhǔn)測試套件。

所以,如果 AngularDart 引入了一個導(dǎo)致 AdWords 加載慢了 1% 的改動,上線這個改動之前他們都會知道。如果十月份 AngularDart 團隊宣布他們自從八月份以來 AngularDart App 比原來體積小了40%,速度快了10%的時候,他們并不是在討論什么 TodoMVC 這樣的小事。他們談?wù)摰氖钦鎸嵤澜缰欣锩嫔习偃f級別用戶的產(chǎn)品和上兆字節(jié)的業(yè)務(wù)邏輯代碼。

順帶提一下:封閉式編譯工具

你可能會好奇,在 AngularDart 引入了一個 bug 后,在這么龐大的內(nèi)部倉庫里面,應(yīng)該去跑哪些測試呢?當(dāng)然不會手動的在 74000 個測試去挑測試來跑,當(dāng)然也不會把 Google 所有的測試都跑一邊。答案在于一個叫做 Bazel 的玩意兒。

在這種代碼規(guī)模下,你不可能寫 Shell 腳本來編譯所有文件,這樣做會很不靠譜而且會慢的令人發(fā)指。這時候你需要的是一個封閉式編譯工具。

“封閉式”(Hermetic)的意思有點類似于函數(shù)式編程里面純函數(shù)的”純“。也就是說,你的編譯過程不能有副作用(如臨時文件,改變環(huán)境變量 PATH 等),他們也必須是確定性的(相同的輸入必須得到同樣的輸出)。這樣的話不管你什么時候在你的機器上編譯和運行測試,你會得到一致的輸出結(jié)果。你不需要make clean。因此你可以發(fā)送你的編譯/測試到編譯服務(wù)器上并行編譯。

Google 花費了數(shù)年的時間開發(fā)這個編譯工具。近些年將其開源了,就是 Bazel

多虧了有這樣的基礎(chǔ)設(shè)施,內(nèi)部測試工具才能夠自己來決定編譯和測試哪些受影響的部分,并且在恰當(dāng)?shù)臅r候運行他們。

以上種種意味著什么?

AngularDart 的目標(biāo)非常明確,就是要在構(gòu)建大型 Web 應(yīng)用領(lǐng)域,做到一流的效率、性能和可靠性。這篇文章所講述的就是后一個部分——可靠性。同時也解釋了為什么 Google 的關(guān)鍵產(chǎn)品,如 AdWords 和 AdSense 都使用這個框架。不是 AngularDart 自己吹噓自己有多么厲害的用戶,就正如上面所說的——就是因為有這么多的內(nèi)部用戶,AngularDart 幾乎不可能引入一些隨意的變更。這也使得這個框架更加的可靠。


(如果你覺得這些聽起來太商業(yè)化了,你可以看一下我的非商業(yè)化 AngularDart 項目 自動化的特朗普 或者 Prime Finder

如果你想要一個每幾個月就有一次大的改版或者重大功能升級的框架,很明顯 AngularDart 并不是你要找的。即使我們想這么干,你看完這篇文章就只知道,這么嚴(yán)格的開發(fā)流程是不可能開發(fā)出這樣的框架的。但我們真心相信,一個不是很流行但是卻很穩(wěn)定的框架肯定會它有發(fā)展和生存的空間。

在我看來,如何判斷一個開源技術(shù)會不會得到長期的維護和支持的,那么你就看看這個技術(shù)是不是維護者的公司業(yè)務(wù)的重要組成部分??梢阅?Android,dagger,MySQL 或者 git 作為例子。這就是我為什么我非常高興地看到 Dart 有一個首選它的 Web 框架(AngularDart)、一個首選它的組件庫(AngularDart Components)、一個首選它的移動框架(Flutter)——所有的這些都被用 Google 的商業(yè)產(chǎn)品中。


我最近正在寫一本《React.js 小書》,對 React.js 感興趣的童鞋,歡迎指點。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容