聊聊Google的工程實(shí)踐(一)
也許你是被標(biāo)題吸引進(jìn)來的,為避免誤解,這里需要事先聲明下,本人從來沒有在谷歌實(shí)習(xí)或者工作過,因此,張小龍的那句話“我所說的,都是錯的”倒是很適合本文。就這個角度而言,標(biāo)題叫做「從Googler學(xué)到的工程實(shí)踐」可能比較恰當(dāng)。
先說一下我的工作經(jīng)歷,我實(shí)習(xí)的第一家公司叫云壤,云壤有很多的ex-Googler,創(chuàng)始人是谷歌中國工程院副院長,他搭建的早期團(tuán)隊,基本都是谷歌工程師。在這家公司工作了兩年的時間。目前所在的公司和谷歌的關(guān)系也比較特殊,不僅CEO和CTO都是谷歌出來的,而且在C輪還拿到了谷歌的投資。前段時間寫過《How Google Works》的施密特,還到我們參觀了一趟。目前我在這家公司呆了大概五年半時間,多少也對谷歌的一些文化有了更多的了解。
除此之外,最近兩年詳細(xì)閱讀了不少jeff dean 和 sanjay ghemawat等谷歌大牛的論文,也算是加深了對谷歌工程文化的理解。
之所以想聊聊這個話題,是因?yàn)檫@幾天我們一個同事翻譯了《Software Engineering at Google》這份文檔。翻譯文章是《Google軟件工程之道》,有興趣的可以閱讀下。下面圍繞這份文檔的話題,聊聊我這幾年的一些理解。
代碼倉庫
谷歌基本使用單一代碼倉庫,大家基于master進(jìn)行開發(fā)。我呆的第一家公司也是這么干的。當(dāng)時我們基于SVN來做代碼管理,我們在master branch上創(chuàng)建了一個experiment目錄,每個人在上面創(chuàng)建一個自己的文件夾,可以把未經(jīng)review的代碼提交到這個文件夾。
單一的一個倉庫當(dāng)然會有很多問題,比如代碼龐大,新人checkout 代碼需要花費(fèi)不少時間,編譯緩慢等。谷歌使用的是分布式編譯系統(tǒng),使用幾百甚至上千臺服務(wù)器,這對絕大部分公司來說都是巨大的成本,非常奢侈。我們當(dāng)時使用的也是分布式編譯,不過主要還是借助每個人的工作電腦。
當(dāng)時云壤有一個目標(biāo)是讓每臺電腦都可以登陸任何人的賬號,隨便換臺電腦都可以開始寫代碼。當(dāng)時使用的是LDAP + NFS,不過早期發(fā)生了很多次故障,導(dǎo)致全公司的人都沒法干活,只能開始聊聊天。當(dāng)時機(jī)器故障幾乎是成為除了TGIF外,最佳的促進(jìn)團(tuán)隊溝通的時間。
單一的代碼倉庫也會有很多問題,比如權(quán)限的管理。你的代碼大家都能看到,這會引入很多的管理成本。比如,有一些代碼庫,你希望大家能夠access最新版本,但是又不想讓代碼被看到,怎么辦?當(dāng)時我們的解決方式是,引入 public/internal 兩個不同的目錄,結(jié)合定制化的SVN 插件和jenkins job來實(shí)現(xiàn)。public 目錄放對外可見的接口,internal 放內(nèi)部實(shí)現(xiàn)。如果你沒有權(quán)限,編譯的時候會依賴上提交上來的pre-build 的靜態(tài)庫。
Git 起來后,使用單一的repo應(yīng)該比較少了,想要強(qiáng)力推動也有很多的顧慮。畢竟,一套應(yīng)對86T代碼的內(nèi)部系統(tǒng),不是任何公司都有能力去開發(fā)的。
在若干年前,百度的基礎(chǔ)架構(gòu)部在推類似的事情,不知道后來是否執(zhí)行了。不過聽說百度內(nèi)部的類gRPC 框架有好幾套,并且沒看出來哪一個有一統(tǒng)百度之勢。這不,后來谷歌自己的gRPC 都開源了。想來在百度要學(xué)習(xí)谷歌的基礎(chǔ)架構(gòu),并不簡單。
構(gòu)建系統(tǒng)
怎么讓編譯變得簡單應(yīng)該是關(guān)注工程師生活質(zhì)量最重要的事情。工程師的生命,除了耗費(fèi)在重新造輪子上外,浪費(fèi)在編譯上的大好年華應(yīng)該也數(shù)不勝數(shù)。當(dāng)你寫好代碼,等待結(jié)果輸出的時候,是最焦灼的,這仿佛一個劍客刺出去一劍,等了半天不知道是否刺中了對方一般。
云壤成立那會,當(dāng)時還沒有開源的Google bazel,所以云壤的同事開發(fā)了一套簡化版本的構(gòu)建系統(tǒng),內(nèi)部名字叫做ymake,支持的語法基本和 bazel 差不多,不過功能稍微少一些,比如還不能支持依賴關(guān)系查詢等。剛到云壤的我,覺得這工具真牛逼,相見恨晚。在這之前,我負(fù)責(zé)一個Windows項(xiàng)目,因?yàn)轫?xiàng)目依賴的開源項(xiàng)目有點(diǎn)多,我花了很多的精力在整理編譯的配置。而有了類似Google構(gòu)建工具的ymake,工程師工作中很少需要去關(guān)注怎么編譯程序,你只需要寫代碼就行了。
我猜想,在Google的bazel 開放出來之前,中國互聯(lián)網(wǎng)上至少出現(xiàn)過十個本土化的模仿者。其中一個叫做blade, 是騰訊開發(fā)的,后來還開源了,使用python實(shí)現(xiàn)。我當(dāng)時還有幸參與了一部分。
那么構(gòu)建系統(tǒng)解決什么問題呢?我認(rèn)為有以下幾個:
讓各個語言有統(tǒng)一的構(gòu)建語法。不過你使用C++還是java,都可以使用bazel build xxx, bazel test xxx 來進(jìn)行編譯或者測試。
簡化編譯需要編寫的內(nèi)容。使用make 工具的話,往往makefile 不忍卒讀,并且比較凌亂,結(jié)構(gòu)化很差。而bazel 的話就很簡單,你只需要寫一個簡單的BUILD 文件就行了,你需要關(guān)注的是編譯類型(靜態(tài)庫,動態(tài)庫,二進(jìn)制文件,測試文件等),文件列表,依賴關(guān)系等。
簡化依賴關(guān)系。比如讓冗余的頭文件無處藏身,嘗試檢測冗余的依賴等。一些實(shí)現(xiàn)文件,沒必要對外暴露,這樣有利于最小化代碼重構(gòu)帶來的影響。
在構(gòu)建工具層面,引入C/C++語言本身缺失的現(xiàn)代編程理念。比如包和可見性。
引入全局的配置信息。比如優(yōu)化選項(xiàng),編譯警告級別等。
方便引入后處理流程。比如引入單元測試運(yùn)行,如果失敗就拒絕合并。比如在執(zhí)行單元測試的同時引入內(nèi)存泄露檢測等。
最大化基礎(chǔ)庫的使用。通過簡單的依賴就可以引用大量基礎(chǔ)代碼,當(dāng)然開發(fā)效率會提到很大的提升。
當(dāng)然,構(gòu)建系統(tǒng)除了節(jié)省你編寫編譯規(guī)則的時間外,非常重要的一點(diǎn)就是節(jié)省你編譯的時間。谷歌內(nèi)部解決這個構(gòu)建問題是好幾個系統(tǒng),包括Blaze, Forge, SrcFS, ObjFS等。
感興趣的同學(xué)可以閱讀以下文章,這個博客還有很多谷歌基礎(chǔ)架構(gòu)的干貨:
https://mike-bland.com/2012/10/01/tools.html
云壤當(dāng)時開發(fā)人員還不算太多,使用的方案是 distcc + ccache。編譯速度也還過得去。
代碼審查
有關(guān)code review,之前寫過一篇文章《代碼審查(Code Review)之道》,沒讀過的可以讀讀。
這里說一下這兩年的新感悟。面試過幾個總監(jiān)候選人,也大概知道code review,但是往往是最原始的review方式,開會的時候,拉著一堆人對著代碼進(jìn)行review。這樣做有一個問題:
1,code review很隨意,無法強(qiáng)制執(zhí)行。開會也浪費(fèi)很多人的時間。
2,comment不夠便捷。
3,缺少一來一回的交互。拉個會,估計大部分人都不敢說話了,大家提出一些問題,只能乖乖認(rèn)錯,技不如人,心理感受也差。
4,開會review無法形成積累,不方便新人從以往review的comment中學(xué)習(xí)經(jīng)驗(yàn)。
5,無法結(jié)合工具做來必要的前置檢查,比如代碼風(fēng)格檢查,內(nèi)存泄露檢查等。
也有不少候選人認(rèn)為,做code review固然有價值,但是項(xiàng)目忙的時候,交付最重要,從而認(rèn)為code review在更重要的交付面前,是必要的犧牲品。
作為一個Leader, 說服老板認(rèn)為到code review的重要性,是他的基本工作。而想辦法解決code review的可行性和推行成本,也是對leader工程經(jīng)驗(yàn)的考驗(yàn)。
當(dāng)然,能做code review的解決方案很多,github 也行,gitlab 也可以,review board 也挺好的,gerrit 也不賴。說一下code review工具需要解決的幾個問題:
1,發(fā)起一個code review,需要發(fā)送必要的通知郵件。內(nèi)容可以是標(biāo)題,更新涉及的文件列表等。
2,可以和持續(xù)集成工具結(jié)合,比如jenkins。解決掉代碼風(fēng)格自動檢查,編譯警告檢查,確保能編譯成功,單元測試成功,甚至確保單元測試沒有內(nèi)存泄露等。如果自動化工具無法檢查出來問題,自動加一個verified狀態(tài),否則拒絕合并,并通知發(fā)起人,這樣不僅可以節(jié)省reviewer的時間,也可以一定程度保證代碼的質(zhì)量。
3,方便對diff,最好是一左一右對比。
4,有簡單的issue概覽和狀態(tài)顯示。方便別人做決策是否能LGTM。
5,可以方便地合并代碼。
再聊聊應(yīng)該花多少時間在code review上。我認(rèn)為一個合格的leader,應(yīng)該花費(fèi)10%以上的時間在code review上。不過最好控制在30%以內(nèi)。我目前公司的CTO,到今天為止,也仍然花一定的時間在code review上。對leader而言,花一定的時間做代碼審查,可以保持對代碼的敏感性,否則沒過幾年,可能C++的新標(biāo)準(zhǔn)和STL的新功能就完全陌生了。此外,做code review,也是重要的新人培養(yǎng)工作。做code review,也能保證對核心系統(tǒng)的細(xì)節(jié)有更深的把控,否則討論技術(shù)解決方案或者是架構(gòu)的時候,就容易落入空對空。
遺憾的是,隨著負(fù)責(zé)事情的增多,往往技術(shù)管理者很難抽出足夠的時間編寫代碼,甚至是做代碼審查。不過即使如此,也應(yīng)該讓整體團(tuán)隊保持對代碼審查這件事情的重視,不能因?yàn)樽约簺]時間了,而導(dǎo)致整個事情放松。應(yīng)該是代碼審查成為工程文化,成為工程師的日常。學(xué)生時代,作業(yè)需要老師批改,學(xué)生在批注中學(xué)習(xí)。成年了,作家在自我review中保持對文字的精益求精,而程序員則在相互的review中成長。reviewer往往是leader,當(dāng)然也可以是同級,甚至可以是自己的下屬。reviewer的級別不是很重要,能不能對你提交的代碼提出有建設(shè)性意見才是最重要的。
說過code review, 其中最基本的環(huán)節(jié)就是代碼風(fēng)格。我這幾年一直使用的是Google的編程風(fēng)格,地址如下:https://google.github.io/styleguide/cppguide.html
Google的風(fēng)格指南,每隔一段時間就會有所變化,因此我們也偶爾會重讀一遍,多讀幾遍,往往也會常讀常新。一般隨著C++標(biāo)準(zhǔn)的更新,Google的C++風(fēng)格指南也會有所更新。有趣的是,我在面試中碰到不少技術(shù)人,說他們有比較豐富的code review經(jīng)驗(yàn),但是當(dāng)我問及他們,一般code style涉及到哪些環(huán)節(jié),他們一般只能想到可憐的三四點(diǎn),不成系統(tǒng)。我認(rèn)識一位編程經(jīng)驗(yàn)很豐富的朋友,他一般會定時閱讀下Google C++編程風(fēng)格的新版本,并且常將C++基礎(chǔ)庫的設(shè)計和其他語言的基礎(chǔ)庫做對比,不同語言相互參照,以獲得更深入的理解,他的這個習(xí)慣非常值得大家學(xué)習(xí)。
有關(guān)代碼風(fēng)格的話題,《代碼大全》這本書也有不少篇幅談及,有興趣的朋友可以找來讀讀。這本書我讀過兩三遍,也算是重讀常新的好書。
由于涉及到的話題比較多,暫時先寫到這里,后續(xù)主要想聊聊以下話題。
軟件測試
調(diào)試與剖析
上線與發(fā)布
代碼重寫
OKR
20%時間
績效考核
TGIF
谷歌的大佬們
推薦閱讀
