干貨分享:六大招教你有效進(jìn)行代碼 Review
研發(fā)同學(xué)都知道代碼 Review 的重要性,在騰訊代碼 Review 也越來(lái)越受大家重視,作為騰訊專有云平臺(tái)研發(fā)的一員,我參與了大量的代碼 Review,明顯地感受到有效的代碼 Review 不但能提高代碼的質(zhì)量,更能促進(jìn)團(tuán)隊(duì)溝通協(xié)作,建立更高的工程質(zhì)量標(biāo)準(zhǔn),無(wú)論對(duì)個(gè)人還是團(tuán)隊(duì)都有著重要的價(jià)值。本文就為什么要做代碼 Review 以及如何有效地做代碼 Review 分享一下個(gè)人的看法。
為什么要做代碼 Review
為什么要代碼 Review,相信每個(gè)人心中都有比較一致的答案,Google 搜索一下也能找到一大堆的文章。這里簡(jiǎn)單總結(jié)幾點(diǎn):
1)提高代碼質(zhì)量
這是代碼 Review 的初衷,也是代碼 Review 最直接的價(jià)值。Reviewers 根據(jù)各自的經(jīng)驗(yàn),思考方式,看問(wèn)題的角度給代碼提出各種可能的改進(jìn)意見,從而形成更好的代碼以及產(chǎn)品質(zhì)量。
我們知道產(chǎn)品問(wèn)題越晚提出解決它的代價(jià)就越大,參與進(jìn)去的人、要走的流程都會(huì)越來(lái)越多。代碼 Review 可以說(shuō)是早期解決問(wèn)題最有效的途徑之一了,在代碼 Review 中解決掉哪怕一個(gè)小問(wèn)題都能避免后續(xù)一堆的麻煩事。
2)形成團(tuán)隊(duì)統(tǒng)一的高質(zhì)量標(biāo)準(zhǔn)
有效的代碼 Review 最終會(huì)在團(tuán)隊(duì)范圍內(nèi)建立起統(tǒng)一的質(zhì)量標(biāo)準(zhǔn),并且會(huì)隨著團(tuán)隊(duì)成員的互相學(xué)習(xí)和促進(jìn)形成更高的標(biāo)準(zhǔn)。參與者會(huì)在代碼 Review 的過(guò)程中基于具體問(wèn)題從不同角度提出改進(jìn)意見,并最終做出當(dāng)前最佳的選擇并形成共識(shí)。隨著代碼 Review 的有效進(jìn)行,團(tuán)隊(duì)成員會(huì)有意識(shí)地關(guān)注代碼質(zhì)量,從而形成越來(lái)越高的事實(shí)上的質(zhì)量標(biāo)準(zhǔn)。
3)個(gè)人快速成長(zhǎng)
通過(guò)有效的代碼 Review,Author 和 Reviewer 都將獲得成長(zhǎng),在 Review 過(guò)程中參與人就具體的問(wèn)題展開深入的討論,無(wú)論是怎么寫出整潔的代碼、怎么更好地更全面地思考問(wèn)題、怎么最佳地解決問(wèn)題,參與人都可以互相取長(zhǎng)補(bǔ)短,互相提高。通過(guò)具體代碼實(shí)現(xiàn)進(jìn)行的討論往往是最深入和有效的,代碼 Review 是開發(fā)者提高代碼能力最重要的途徑之一。
有的人認(rèn)為代碼 Review 就是 Reviewer 幫助查找代碼中的 Bug,其實(shí)代碼 Review 不應(yīng)該是一個(gè)單向的過(guò)程,而應(yīng)該是個(gè)雙向交流的過(guò)程,Reviewer 幫助 Author 提出代碼改進(jìn)意見的同時(shí),也是向優(yōu)秀的 Author 學(xué)習(xí)的過(guò)程。我們都知道提高代碼能力一個(gè)有效的途徑是閱讀優(yōu)秀的項(xiàng)目代碼,但是閱讀項(xiàng)目代碼有著不小的難度,這也是大部分人沒有去執(zhí)行的原因,而 Review 資深同事的代碼,我們?cè)陂喿x代碼的同時(shí)能夠直接進(jìn)行有效的溝通,這是一個(gè)快速有效的學(xué)習(xí)途徑,尤其對(duì)開發(fā)經(jīng)驗(yàn)還不算豐富的開發(fā)者而言。
如何做好代碼 Review
2.1. 第一招:什么時(shí)候發(fā)起 Review
在代碼 Review 上,Author 需要意識(shí)到:Reviewer 的時(shí)間是昂貴的。因此在正式邀請(qǐng) Reviewer 發(fā)起代碼 Review 前,Author 有幾項(xiàng)需要注意的點(diǎn),這些都能提高代碼 Review 的效率,節(jié)省 Reviewer 的時(shí)間。
2.1.1. MR (Merge Request)
也稱為 PR(Pull Request), MR 是我們進(jìn)行代碼 Review 的地方,它記錄著代碼的具體改動(dòng),參與者具體的討論過(guò)程。好的 MR 應(yīng)該做到以下幾點(diǎn):
- 單一:一個(gè) MR 應(yīng)該只解決一個(gè)單一的問(wèn)題,無(wú)論是修復(fù)一個(gè) bug,還是實(shí)現(xiàn)一個(gè)新 feature。Author 應(yīng)該避免一個(gè) MR 包含不同意圖的代碼改動(dòng)。單一的 MR 能幫助 Reviewer 快速地了解代碼改動(dòng)的動(dòng)機(jī),能有針對(duì)性地進(jìn)行 Review。
- 短小:MR 應(yīng)該盡量地小,比如一個(gè) feature 引入了較多的改動(dòng),需要考慮是否可以拆成獨(dú)立的幾塊實(shí)現(xiàn),分開提 MR,比如接口定義、接口實(shí)現(xiàn)、邏輯對(duì)接等拆分開。
- 詳細(xì): 這里說(shuō)的詳細(xì)是指 MR 應(yīng)該盡可能地詳細(xì)描述它的背景和動(dòng)機(jī),可以是在 MR 的描述中詳細(xì)體現(xiàn),也可以是連接到具體 issue 或 tapd 單中。需要達(dá)到的目的是,其他人翻開一個(gè) MR 能知道當(dāng)時(shí)做這個(gè)改動(dòng)的背景以及動(dòng)機(jī)。
2.1.2. Commit Message
之前翻看了不少現(xiàn)存的項(xiàng)目代碼,看到不少的 Commit Message 寫得比較簡(jiǎn)單,例如一連串的 "update", "fix",從這些 Commit Message 中完全看不出做了什么改動(dòng),想想如果之后想要定位之前的某個(gè)改動(dòng),該從哪里下手。
目前 Commit Message 規(guī)范比較常見的有 Angular 團(tuán)隊(duì)的規(guī)范,并由此衍生出了 Conventional Commits Specification,可以參照此 Specification 約定 Commit Message 格式規(guī)范。
- <type>(<scope>): <subject><BLANK LINE><body><BLANK LINE><footer>
大體分三行:
- 【標(biāo)題行】 必填, 描述主要修改類型和內(nèi)容。
- 【主題內(nèi)容】 描述為什么修改, 做了什么樣的修改, 以及這么做的思路等等。
- 【頁(yè)腳注釋】 放 Breaking Changes 或 Closed Issues
其中 type 是 Commit 的類型,可以有以下取值:
- feat:新特性
- fix:修改 bug
- refactor:代碼重構(gòu)
- docs:文檔更新
- style:代碼格式修改
- test:測(cè)試用例修改
- chore:其他修改, 比如構(gòu)建流程, 依賴管理
其中 scope 表示的是 Commit 影響的范圍,比如 ui,utils,build 等,是一個(gè)可選內(nèi)容。
其中 subject 是 Commit 的概述,body 是 Commit 的具體內(nèi)容。
例如:
- fix: correct minor typos in codesee the issue for details on typos fixed.Refs #133
Commit Message 可以在 git 中配置模板,這樣可以在 vim 中展示出模板,另外可有工具幫助我們生成和約束 Commit Message,例如 commitizen/cz-cli,這里不再具體說(shuō)明。
2.1.3. CI 通過(guò)
CI(Continuous Integration),持續(xù)集成可以幫助我們自動(dòng)發(fā)現(xiàn)很多代碼中的基本問(wèn)題,在合適的靜態(tài)代碼檢查(lint)配置和良好的單元測(cè)試覆蓋下,CI 可以有效地提高代碼的質(zhì)量。很多人都低估了靜態(tài)代碼檢查的能力,實(shí)際上現(xiàn)在常見語(yǔ)言的靜態(tài)代碼檢查已經(jīng)能幫助發(fā)現(xiàn)不少的 bug 和隱患。對(duì)于 Go 語(yǔ)言,可以配置 golangci-lint 來(lái)做代碼檢查,單元測(cè)試根據(jù)實(shí)際情況可以制定相應(yīng)的標(biāo)準(zhǔn),比如覆蓋率 60%,其中關(guān)鍵的代碼邏輯盡量全面覆蓋。
提交代碼 Review 前需要確保 CI 執(zhí)行通過(guò),這也是為了節(jié)省 Reviewer 的時(shí)間,能夠通過(guò)自動(dòng)化解決的事情,盡量不要讓 Reviewer 來(lái)做,而 Reviewer 發(fā)現(xiàn) CI 未過(guò)的 MR 也可以要求 Author 先解決 CI 問(wèn)題。
2.1.4. Self-Review
我們一般代碼 Review 都是找他人來(lái)進(jìn)行 Review,其實(shí)負(fù)責(zé)任的 Author 在邀請(qǐng)他人來(lái)代碼 Review 前也需要自己簡(jiǎn)單 Review 一遍,即 Self-Review。
Self-Review 的目的包括:
- 發(fā)現(xiàn)那些明顯的疏忽,如代碼 debug 過(guò)程中留下的不必要的痕跡,比如 fmt.Println(...),不小心注釋掉的代碼。
- 之前被 Reviewer 多次提出過(guò)的問(wèn)題。
- Commits 是否正常,在多人協(xié)作的情況下 MR 中否帶入了不相關(guān)的 Commit。
- Commit Message 是否合適。
Self-Review 是一個(gè)非常快速的過(guò)程,從我個(gè)人的經(jīng)驗(yàn),一般 1-2 分鐘即可完成,所以推薦大家養(yǎng)成 Self-Review 的習(xí)慣。
2.2. 第二招:該找誰(shuí)來(lái) Review
從目的出發(fā),可以從以下幾方面考慮 Reviewer:
- 提高代碼質(zhì)量。所以首先應(yīng)該找和代碼改動(dòng)緊密相關(guān)的研發(fā)人員參與 Review,比如一起開發(fā)某個(gè)功能,某個(gè)項(xiàng)目,或者一起參與了方案設(shè)計(jì)討論并給出了有價(jià)值意見的研發(fā)。
- 獲取意見。找有相關(guān)經(jīng)驗(yàn)的資深研發(fā)幫忙 Review,比如 Java 語(yǔ)言資深的研發(fā)、寫過(guò)相同或類似系統(tǒng)/功能的研發(fā)。
- 形成共識(shí)。如果涉及到不同團(tuán)隊(duì)或模塊間的接口改動(dòng),或其他會(huì)影響其他人的改動(dòng),可以邀請(qǐng)相關(guān)團(tuán)隊(duì)或模塊的接口人參與 Review,以對(duì)改動(dòng)形成共識(shí)。
- 質(zhì)量把關(guān)。對(duì)于重要的代碼庫(kù),可能會(huì)執(zhí)行比較嚴(yán)格的質(zhì)量把控,如果設(shè)置了必須的 Reviewer,這些 Reviewer 也會(huì)參與進(jìn)來(lái)。
- 變動(dòng)告知。很多情況下一個(gè)代碼庫(kù)可能只有一個(gè)人維護(hù),如果做了些比較特殊的變動(dòng),其他人很難發(fā)現(xiàn)。因此在做一些重要的但是理解起來(lái)不那么直接的地方的時(shí)候,最好告知一下相關(guān)的研發(fā),以便他們能大概知道發(fā)生了什么。
2.3. 第三招:都 Review 些什么
經(jīng)常會(huì)有 Reviewer 拿到 MR 不知道該 Review 些什么,其實(shí)無(wú)論你參與對(duì)應(yīng)項(xiàng)目的深入如何,都可以對(duì)代碼進(jìn)行 Review,也鼓勵(lì)不同人從不同的深度、角度去幫助 Review。代碼 Review 沒有固定的形式,它更像是一門藝術(shù),唯一的提高辦法就是實(shí)際參與進(jìn)去。
Review 的時(shí)候可以從以下幾個(gè)方面入手:
1)簡(jiǎn)單的 Review
在 CI 通過(guò)的情況下,最簡(jiǎn)單的 Review 方式可能只需要這樣:
Reviewer:在實(shí)際環(huán)境中都驗(yàn)證過(guò)了嗎?
Author:當(dāng)然驗(yàn)證過(guò)了
Reviewer:LGTM
這是一種提醒式的 Review。確認(rèn)一句:是否在環(huán)境中驗(yàn)證過(guò)了,或者進(jìn)一步把能想到的重要的驗(yàn)收點(diǎn)提出來(lái)確認(rèn)一遍。即使是這種最簡(jiǎn)單的 Review 實(shí)際上也是有價(jià)值的,我們很難保證所有研發(fā)都會(huì)在提 MR 前實(shí)際在環(huán)境中驗(yàn)證自己所做的修改,也很難保證單元測(cè)試、e2e 測(cè)試能 Cover 住所有的情況,Reviewer 基本上也不可能都自己去環(huán)境上跑一遍。讓 Author 去確認(rèn)實(shí)際上就是提醒 Author 去確保改動(dòng)至少是真實(shí)有效的,尤其是對(duì)一些已發(fā)布版本的 Bugfix,一定要提醒實(shí)際自測(cè)通過(guò)。
類似的提醒還包括:相關(guān)的文檔(外部的)是否相應(yīng)更新了、這個(gè)改動(dòng)是否會(huì)有兼容性的問(wèn)題、性能是否有影響。他們的本質(zhì)就是提醒 Author 自己去思考他們可能遺漏的問(wèn)題。
2)常規(guī)的 Review
代碼 Review 一般都會(huì)從代碼風(fēng)格、變量命名、語(yǔ)法統(tǒng)一處入手,當(dāng)然這些應(yīng)該更多的借助于 CI 等自動(dòng)化手段來(lái)保證,但是在相關(guān)流程還不是很完善的前提下還是有必要進(jìn)行關(guān)注。
此外代碼可讀性、代碼健壯性、代碼可擴(kuò)展性都是 Review 時(shí)關(guān)注的點(diǎn)。每一個(gè)關(guān)注的點(diǎn)都依賴于 Reviewer 的實(shí)際經(jīng)驗(yàn),這里只簡(jiǎn)單舉幾個(gè)例子:
{ 代碼可讀性 }
代碼是寫給人看的,因?yàn)闆]有不需要維護(hù)的代碼,無(wú)論是 Author 自己后續(xù)維護(hù)代碼,還是他人接手代碼,能快速地理解代碼邏輯是非常必要的。
代碼 Review 的時(shí)候可以關(guān)注以下幾點(diǎn):
- 變量、方法的命名是否合適,是否真實(shí)反映了他們的目的,這方面網(wǎng)上可以找到不少的資料說(shuō)明。
- 實(shí)現(xiàn)的邏輯是否已有現(xiàn)成的庫(kù)可以替代。如果有成熟的庫(kù)可以使用,盡量不要自己去實(shí)現(xiàn),因?yàn)榭赡軙?huì)引入不必要的 bug。從我個(gè)人的角度,簡(jiǎn)潔(大白話就是代碼少)是可讀性一個(gè)很重要的指標(biāo)。
- 關(guān)于注釋。代碼注釋不求多,而在于有效,以前也經(jīng)歷過(guò)代碼注釋要求至少達(dá)到 30% 以上的年代,現(xiàn)在看來(lái)過(guò)多依賴注釋其實(shí)是對(duì)代碼質(zhì)量的不自信,好的代碼應(yīng)該盡量做到自解釋。在實(shí)際過(guò)程中偶爾能看到代碼邏輯實(shí)際已經(jīng)清晰明了,但是用語(yǔ)句不怎么通的英文注釋了一通,最后反而是看懂了代碼才能理解注釋到底說(shuō)了啥。因此 Review 的時(shí)候,不必要的注釋可以建議去掉。
- 不好理解的地方要有恰當(dāng)?shù)淖⑨?。代碼中如果有特殊處理、特殊的常量、或者不符合一般用法的邏輯需要特別注釋說(shuō)明一下。
- 代碼的組織。良好的代碼應(yīng)該有較好的封裝以及層級(jí),使得代碼看起來(lái)清晰明了,比如 DAO 層、Service 層。
{ 代碼健壯性 }
- 代碼的改動(dòng)是否會(huì)影響其他功能。
- 用戶參數(shù)是否做好細(xì)致的校驗(yàn)。
- 有沒有 Panic 的可能(針對(duì) Go 的說(shuō)法)。
- 是否會(huì)破壞 API 的兼容性。
{ 可擴(kuò)展性 }
當(dāng)前的實(shí)現(xiàn)方式是否能兼容以后類似的擴(kuò)展需求。比如在新增接口、API 或者調(diào)整參數(shù)以解決某一問(wèn)題上,可以考慮是否后續(xù)會(huì)有其他類似情況發(fā)生。舉幾個(gè)例子:
- 假設(shè)我們需要定義一個(gè) API 接口去獲取一個(gè)用戶的某些信息,例如聯(lián)系方式等,我們定義的 API 就不能只返回這些信息,而是應(yīng)該把用戶相關(guān)的信息都返回。
- 假設(shè)我要定義一個(gè)參數(shù),雖然當(dāng)前定義成單個(gè)元素即可滿足,例如 string, int,但是以后是否會(huì)涉及到多個(gè)元素的場(chǎng)景,是否定義成 []stirng, []int 是更優(yōu)的。
這里只是舉了有限的一些例子,在實(shí)際 Review 過(guò)程中,Reviewer 可以根據(jù)自身的經(jīng)驗(yàn)從各個(gè)角度提出優(yōu)化的意見。一般需要重點(diǎn)看看:
- 你看不懂或疑惑的地方。
- 打破你常規(guī)的地方。
- 復(fù)雜的地方。
2.4. 第四招:如何進(jìn)行
Review 過(guò)程中鼓勵(lì) Reviewer 大膽 Comment,有不理解的地方,或者覺得不合適的地方都直接表達(dá)出來(lái),Author 對(duì) MR 的 每個(gè) Comment 也要做出反饋,無(wú)論是展開討論還是簡(jiǎn)單的給個(gè) OK 都是有效的反饋。
Review 的過(guò)程可以是:
- Author 在各項(xiàng)確認(rèn)工作完成后,發(fā)起 Review,如果比較急,可以給重要的 Reviewer 發(fā)消息請(qǐng)求幫忙 Review。
- Reviewer 看到 MR 后應(yīng)該先確認(rèn) MR 的背景和目的,如果不清楚也無(wú)法從 MR 中獲取該信息,最好直接和 Author 溝通。
- Reviewer 直接在 MR 上提出自己的建議或者問(wèn)題。
- Author 對(duì)每個(gè) Comment 進(jìn)行反饋,并展開必要的討論。
- 復(fù)雜的話題可以采用線下討論以提高溝通效率。
- Author 處理完了所有的 Comment,也修改了代碼后,需要在 MR 里 @ 一下相關(guān) Reviewer 告知所有優(yōu)化已經(jīng)提交,如果時(shí)間比較急也可以直接和 Reviewer 溝通。
- Reviewer 確認(rèn)沒問(wèn)題,給 MR 進(jìn)行 Approve,一般簡(jiǎn)單的回復(fù)是 LGTM(Lood Good To Me),也可以對(duì) Author 的工作進(jìn)行贊賞,比如 “God Job” 等。
- Approve 后 MR 由誰(shuí)來(lái)合并這個(gè)看自己選擇。
- 如果 Reviewer 提供了很多有用的建議幫助優(yōu)化代碼,Author 也可以禮貌性地感謝一下 Reviewer。
2.5. 第五招:關(guān)于 Comment
代碼 Review 很大程度上就是給代碼挑毛病,而且一般預(yù)期挑的越多越好,但代碼是 Author 寫的,很多情況下或多或少會(huì)對(duì) Author 造成不適。所以在 Comment 的時(shí)候需要盡量注意措辭,尤其是一些主觀的東西,一般以建議的方式給出。當(dāng)然對(duì)于原則性的問(wèn)題,是要堅(jiān)守質(zhì)量標(biāo)準(zhǔn)的,甚至可以直接 Deny 掉 MR。
另外,參與者也不要吝嗇你的溢美之詞,無(wú)論是 Author 還是 Reviewer,在 Review 中發(fā)現(xiàn)了好的地方或好的建議,請(qǐng)給予對(duì)方以肯定和贊美,這樣有助于 Review 有效的進(jìn)行。
2.6. 第六招:參與者該抱著怎樣的心態(tài)
2.6.1. Author
首先需要明確一點(diǎn),是 Author 自己對(duì)代碼的質(zhì)量負(fù)責(zé),因此應(yīng)當(dāng)懷著感恩的心去看待堅(jiān)持認(rèn)真幫你 Review 代碼的 Reviewer,因?yàn)椴⒉皇撬心慵拥?Reviewer 都有時(shí)間和精力來(lái)幫你提高代碼質(zhì)量。
也正是因?yàn)?Author 是自己代碼的 owner,所以不要依賴于 Reviewer 去幫你守代碼質(zhì)量的大門,像 “代碼 Review 的時(shí)候你怎么就沒看出來(lái)”,“這不是你建議我這么做的嗎” 這樣的話千萬(wàn)別說(shuō)。Reviewer 只是幫你提高代碼質(zhì)量,因此我們?cè)撟龅墓ぷ鞫家プ?,比如?xì)致的 Reviewer 可能某些情況下直接提出了代碼優(yōu)化的建議,Comment 時(shí)貼上了優(yōu)化的代碼片段,Author 不能直接假設(shè)它一定是能正常工作的,而應(yīng)該對(duì)它進(jìn)行完整的驗(yàn)證。
對(duì) Reviewer 給出的 Comment,不要有抵觸的情緒,對(duì)你覺得不合理的建議,可以委婉地進(jìn)行拒絕,或者詳細(xì)說(shuō)明自己的看法以及這么做的原因。有時(shí)候一個(gè)你覺得不合理的建議可能代表一個(gè)新的思考角度,也可能代表 Reviewer 自身代碼能力上的不足,無(wú)論是哪個(gè),無(wú)論最終是 Author 還是 Reviewer 得到了提高,都應(yīng)該是好事。
2.6.2. Reviewer
Review 代碼既是幫助提高代碼質(zhì)量的過(guò)程,也是 Reviewer 提高自己代碼能力和溝通能力的過(guò)程。因此應(yīng)該在 Review 的同時(shí)保持一個(gè)學(xué)習(xí)者的心態(tài),既要發(fā)現(xiàn)對(duì)方代碼中的缺陷,也要努力去發(fā)現(xiàn)代碼中的亮點(diǎn)。切忌單純以挑毛病的心態(tài)去 Review 代碼。
有不少 Reviewer 在寫 Comment 的時(shí)候會(huì)猶豫,擔(dān)心自己提出的問(wèn)題或建議比較“蠢”,因此猶猶豫豫下看完了代碼抱著一堆疑慮最終啥也沒留下。其實(shí)在代碼 Review 的時(shí)候大可不必有什么心里負(fù)擔(dān),有什么疑惑的、不清楚的地方或者有什么自己的想法,可以直接提出來(lái),有時(shí)候一個(gè)簡(jiǎn)單的 Comment 就可能會(huì)激起 Author 和你的 Comment 毫不相干的新思路。再者你即使沒幫 Author 提高代碼,讓 Author 幫你提高思考不也是件不錯(cuò)的事情嗎。
Reviewer 也不需要去關(guān)注自己的“產(chǎn)出”,并不是一定要提出一堆問(wèn)題才是好的代碼 Review,Author 代碼寫得很棒也是很正常的,如果從你的角度覺得代碼沒問(wèn)題,大膽給個(gè) LGTM 甚至是 Approve。