01 Nov 2020 每天我都要做很多次 code review,但到底怎樣 code review 才是好的呢?這篇文章是我閱讀 Google 的「How to do a code review」的筆記還有自己的小感想。 Code Review 的標(biāo)準(zhǔn)做每件事情都一定是有個(gè)終極目標(biāo)的,那麼 code review 的目標(biāo)就是能提高程式庫(code base)品質(zhì)。 基本原則
指導(dǎo)、教學(xué) (Mentoring)在 code review 時(shí)在 comment 分享新知能幫助開發(fā)者能寫出更好的程式碼。注意,若只是純分享而非要在本次 CL 做修改,要在 comment 前加上 Nit。 以下列出一些注意事項(xiàng)…
解決衝突這裡指的衝突是人和人溝通上的衝突,不是 code 要怎麼 merge 或 rebase 的問題。若有衝突的話,審閱者和開發(fā)者必須根據(jù) The CL Author’s Guide 和 Reviewer’s Guide 來達(dá)成共識(shí)。建議面對(duì)面或視訊會(huì)議來做溝通,而不是只是像網(wǎng)友般用 comment 的文字溝通而已。如果只是像網(wǎng)友般用 comment 的文字溝通,那也要將每次討論的結(jié)論紀(jì)錄下來也放在本次 CL 的 comment 裡面,給之後的人參考用。 若還是無法達(dá)成共識(shí)呢?就只能尋求更高位子、能做決策的人的支援了,例如:找 technical leader 一同與團(tuán)隊(duì)討論,或要求程式碼的維護(hù)者來做決定;又或要求技術(shù)經(jīng)理(engineering manager)來幫忙做決定。千萬不要就讓這個(gè) CL 躺著躺到天荒地老、無法結(jié)案。 Code Review 到底要看什麼?設(shè)計(jì) (Design)最重要的是「設(shè)計(jì)」。 這次 CL 的程式碼是有意義的嗎?我看過不少程式碼是在寫廢話的更動(dòng)的範(fàn)圍是什麼-是更動(dòng)到專案的 codebase 還是函式庫 (library) 呢? 與系統(tǒng)整合的狀況好嗎?千萬不要改 A 壞 B 啊現(xiàn)在加上這個(gè)新功能是好的時(shí)機(jī)點(diǎn)嗎?(這個(gè)問題是比較高層次的問題,我個(gè)人是認(rèn)為這應(yīng)該在 design review 時(shí)提出來,而不是 code review 階段,況且也很容易拖延這個(gè) CL 的 code review) 功能 (Functionality)這次 CL 是否達(dá)到開發(fā)者想要完成的功能?對(duì)使用者來說,這是好用的功能嗎?對(duì)未來的開發(fā)者來說,這段程式碼好維護(hù)、好擴(kuò)充嗎?我們會(huì)預(yù)期開發(fā)者在提交 CL 做 code review 前已完整測試過自己撰寫的程式碼,而審閱者也必須來幫忙做測試,甚至是幫忙測試極端的狀況 (edge case),例如:同步的問題、把自己當(dāng)使用者來用用看、掃除可見的 bug。把自己當(dāng)使用者來測試這段程式碼是很重要的,因?yàn)?UI 的變化不見得能從閱讀程式碼中完全看出來…因此若無法完全掌握細(xì)節(jié),也可以請(qǐng)開發(fā)者來做個(gè) demo。 另一個(gè)很難從 code review 中閱讀程式碼時(shí)看出的問題是 deadlock 或 race condition,因此需要開發(fā)者與審閱者一同留心這個(gè)問題,這也是為何不推薦 concurrency model 的原因,因?yàn)檫@類複雜度較高都情況,code review 是無法檢查出來的。 複雜度 (Complexity)本次 CL 的程式碼可以更精簡嗎?不管是從 function 或 class 的角度來看,是否太複雜呢?「複雜」通常代表「難以讀懂」,更代表未來的維護(hù)者「容易讓它產(chǎn)生 bug」。 另外一個(gè)議題是過度設(shè)計(jì) (over-engineering) ,「過度設(shè)計(jì)」常會(huì)使得程式碼變得複雜,像是嘗試變得更通用或是新增了目前用不到的功能。審閱者要特別注意過度設(shè)計(jì)的問題,鼓勵(lì)開發(fā)者先專注在解決目前的問題上,而不是考慮到超遠(yuǎn)以後的事情…因?yàn)槲磥淼氖虑槎颊f不準(zhǔn)啊,誰能知道未來會(huì)有什麼樣的需求呢?尤其是在尚未找到產(chǎn)品存活的目標(biāo)、TA 的口味、市場的需求的時(shí)候,說不定產(chǎn)品很快就倒了 測試 (Tests)本次 CL 是否有 unit test、integration test 或 end-to-end test 這些測試程式呢?除非是緊急狀況,每個(gè) CL 應(yīng)該要有相對(duì)應(yīng)的測試程式。而測試程式不能只是花瓶、好看而已,必須是真的有用的、正確的、合理的。例如:測試碼偵測的錯(cuò)誤時(shí),可以準(zhǔn)確斷定目前程式碼真的有問題;測試的斷言必須簡單有用;使用不同的測試方法來做測試以試圖面面俱到…千萬不要覺得測試程式碼的重要性遠(yuǎn)低於開發(fā)功能,而不需要付出人力時(shí)間來做維護(hù)。 命名 (Naming)工程師十大難題之一就是「命名」,審閱者要好好幫開發(fā)者檢查有沒有取個(gè)合理的名字。一個(gè)好的名字要足以讓大家明白「它是什麼」或是「它要做什麼」,而不是一堆字合起來但完全看不懂是什麼意思。 例如:要做字串處理,比起 doSomething,formatString 一定是好很多的…
註解 (Comments)開發(fā)者所寫的註解是否清楚易懂、皆為必要?註解通常要寫的是為何這段程式碼要存在,像是當(dāng)初的決策等,而不是寫它在做什麼,因?yàn)槿绻€要寫註解才能讓人知道它怎麼運(yùn)作就是程式碼寫得很複雜、難以讀懂的意思,必須要改善程式碼使之精簡好讀(回到上一小節(jié)「[複雜度(#複雜度-complexity)」那部份來做檢閱)。當(dāng)然有些狀況是不得已必須要解釋程式碼的運(yùn)作方式的,像是內(nèi)含困難的 regular expression 或演算法,但多數(shù)狀況的註解是可以寫得更好理解的。 對(duì)於先前的註解也可以重新做檢視,看看要不要做刪除或修改。 注意,不要把註解當(dāng)文件來寫,註解應(yīng)該表達(dá)的是為什麼這段程式碼要存在、怎麼用和使用的時(shí)機(jī),若需要更多的說明可以考慮撰寫 wiki page 或 readme。 撰寫風(fēng)格 (Style)可參考 Google 的 style guide。 請(qǐng)開發(fā)者務(wù)必遵守 style guild 的規(guī)範(fàn),若 CL 的撰寫方式在目前的 style guild 沒有提到,而 review 有建議想要提出,可以在 comment 前加上「Nit:」,畢竟是沒先講清楚的,不要造成這個(gè) CL 被卡著而不能進(jìn) code。 而針對(duì) style 相關(guān)的調(diào)整,開發(fā)者必須另外開一個(gè) CL 來做變更,這樣分開目的所提出的 CL 是比較容易了解到底改什麼、為什麼原因而改,而且之後若想要 revert / cherry-pick 這個(gè) CL 也是比較容易的。 風(fēng)格一致 (Consistency)如果現(xiàn)存在 codebase 裡面的程式碼與 style guild 的規(guī)範(fàn)不同,該怎麼辦呢?除非這樣的修正會(huì)讓程式碼造成誤解,否則就把它調(diào)整成符合規(guī)範(fàn)的樣子吧!不然規(guī)範(fàn)就形同虛設(shè)了。 如果開發(fā)者所撰寫的方式在 style guild 沒有規(guī)範(fàn)到,那就跟現(xiàn)存在程式庫的寫法一致即可;不然,開發(fā)者可以發(fā)起一個(gè) bug or TODO(我認(rèn)為可以是 issue or 維護(hù)的 task)來處理現(xiàn)存在 codebase 的程式碼。 文件 (Documentation)將本次 CL 的變更,同時(shí)更新在文件上;若移除某些程式碼或功能,建議同時(shí)移除這份文件;若沒有文件,要想辦法生出來。 讀懂每一行仔細(xì)閱讀本次 CL 的每一行程式碼,並且一定要能讀懂,千萬不要只是掃過一次(魔鬼藏在細(xì)節(jié)裡)。若很難讀懂、花了太多時(shí)間導(dǎo)致延誤 review 的時(shí)間,應(yīng)該要先通知開發(fā)者可能要花更多時(shí)間來做 code review,並且找開發(fā)者來說明到底是在寫什麼,然後再繼續(xù)做 code review。在 Google 上班的工程師基本上程度都不會(huì)太差,所以寫得讓人看不懂一定是開發(fā)者的錯(cuò),也就是回到前面所說的,太複雜的程式碼可能會(huì)產(chǎn)生更多 bug…就要讓開發(fā)者來看看能不能改善了。當(dāng)然也有可能是審閱者的人程度不夠,那就找一個(gè)程度更好的人來看。 檢視關(guān)聯(lián)部份 (Context)不要只是看更動(dòng)的片段,而是要看所有有關(guān)聯(lián)的程式碼,甚至是整個(gè)/多個(gè)檔案,來評(píng)估這個(gè)變更是否合理。例如:看完整個(gè)檔案而不是這次變更的幾行,會(huì)發(fā)現(xiàn)可以重構(gòu)、切割為更多更小的函式;思考這次變更對(duì) codebase 的健康是否有幫助?如果會(huì)讓 codebase 變得更糟,那麼 CL 就不能被接受 —「羅馬不是一天造成的」codebase 會(huì)愈來愈糟往往是因?yàn)橐淮斡忠淮涡⌒〉脑愀獾淖兏鄯e而成的。 適度的鼓勵(lì):你做得很好 ??若在 CL 中看到不錯(cuò)的地方,千萬不要吝於對(duì)開發(fā)者表達(dá)讚美。code review 不是只針對(duì)改進(jìn)錯(cuò)誤而已,鼓勵(lì)也很重要。在指導(dǎo)開發(fā)者上,告訴開發(fā)者什麼是對(duì)的,比糾正錯(cuò)誤還來得重要。 總結(jié)針對(duì)「Code Review 到底要看什麼」,總結(jié)身為 reviewer 必須確保以下事項(xiàng)
如何有效做 code review目前已知在 code review 時(shí)要看哪些重點(diǎn)了,那麼…要怎麼有效的 review 分散在多個(gè)檔案的 CL 呢? 總結(jié)應(yīng)該要注意的點(diǎn)…
第一步:先大致瀏覽整個(gè) CL大致瀏覽 CL 的描述(通常我都會(huì)圖文並茂的敘述這個(gè)功能或修改的狀態(tài),讓審閱的人能快速進(jìn)入狀況)和更改的程式碼,先思考「這個(gè)變更是合理的嗎?」假設(shè)不合理,一定要馬上跟開發(fā)者解釋原因,並且務(wù)必在拒絕(reject)CL 前提出更好的建議。例如,你可以這樣說「謝謝你做了這麼多的努力,但我們現(xiàn)在正在刪除你剛剛對(duì)於本次 FooWidget 的修改…這裡暫時(shí)不打算做任何調(diào)整,但你可以重構(gòu) BarWidget 嗎?」這樣拒絕開發(fā)者所提出的 CL 並提出更好的建議是禮貌的,而這樣的禮貌很重要,因?yàn)檫@表示儘管不認(rèn)同但依然尊重每個(gè)開發(fā)者。在我看來,在指出錯(cuò)誤時(shí)必定要再給予方向,才是有建設(shè)性的建議。 如果收到很多的 CL 但都不是想要改善的部分,這代表必須重整團(tuán)隊(duì)的開發(fā)流程或考慮外部的貢獻(xiàn)者所提出的已知的作業(yè)方式,再經(jīng)過更多的溝通才做修改、提出 CL。因?yàn)楸绕鹱隽撕芏喙ぷ鞯痪芙^,讓大家知道「不要做什麼」才是比較好的。 第二步:檢視這個(gè) CL 最重要的部份找出這個(gè) CL 最重要的部份,通常會(huì)有一個(gè)檔案有最多邏輯上的變更的,那就是這次 CL 最應(yīng)該要著重的部份,先檢視這部份,這會(huì)對(duì)其他部份給予上下文的線索並加快 review 速度。但如果這個(gè) CL 真的太大而導(dǎo)致看不出哪塊最重要,可以詢問開發(fā)者提供建議應(yīng)該要看哪部份,或要求開發(fā)者把本次 CL 切成多個(gè) CL 方便 review。 若在 CL 中看到一些重大的設(shè)計(jì)方面的問題,就算還沒有 review 完全部的 CL,都應(yīng)該要馬上告知開發(fā)者在設(shè)計(jì)不佳的狀況下,產(chǎn)出的東西根本不會(huì)符合實(shí)際要求,要直接 reject 並且請(qǐng)開發(fā)者做調(diào)整,不然繼續(xù)做 code review 根本就是浪費(fèi)時(shí)間。 為什麼審閱者要馬上通知開發(fā)者呢?原因如下
第三步:依照適當(dāng)?shù)捻樞蚩雌漯N的部份一但確認(rèn)沒有設(shè)計(jì)上的大問題後,接下來就依照邏輯順序檢視個(gè)別檔案,並且要確保沒有漏掉任何一個(gè)檔案。通常在 review 完最重要的那個(gè)部份後,就可以輕鬆地用一些 code review 工具來做檢視。 測試程式通??梢愿嬖V我們大致上是做了什麼更動(dòng),因此先閱讀測試程式也有助於了解主要的程式碼。 Code Review 的速度Google 做了開發(fā)團(tuán)隊(duì)的生產(chǎn)速度的優(yōu)化,而不是優(yōu)化單一開發(fā)人員編寫程式碼的速度。個(gè)人的開發(fā)速度很重要,但團(tuán)隊(duì)的開發(fā)速度更重要。 當(dāng) code review 速度慢時(shí),可能是發(fā)生了以下的狀況:
Code Review 的速度或頻率應(yīng)該怎樣呢?假設(shè)你不是專心正在做某個(gè)特定 task,那麼應(yīng)該可以在 CL 發(fā)出後不久的時(shí)間內(nèi)開始進(jìn)行 code review。最多在「一個(gè)工作天」就要回覆這個(gè) CL,也就是收到 CL 的隔天早上的第一件事就是回覆 CL。如果有需要的話,應(yīng)該在一天之內(nèi)有多輪審核。 速度與中斷讓開發(fā)者不停的 context switch 會(huì)減損產(chǎn)能,也就是說,打斷正在進(jìn)行的工作後需要一段時(shí)間才能恢復(fù)到專注時(shí)的產(chǎn)能,因此不建議為了做 code review 而打斷當(dāng)前的開發(fā)工作,這時(shí)候是可以請(qǐng)被 review 的開發(fā)者稍待 code review 的回覆。為了不打擾開發(fā)者的開發(fā)工作,開發(fā)者可以選擇工作告一段落、午餐後或是會(huì)議結(jié)束後來做 code review。 快速回應(yīng)code review 應(yīng)在意的是回應(yīng)的速度,而不是在意這整個(gè) CL 通過 review 的整個(gè)時(shí)間。當(dāng)然這整個(gè) CL 通過 review 的時(shí)間也應(yīng)該要是快的,但審閱者能(多輪)快速回應(yīng)開發(fā)者是更重要的事情,而且這樣快速的回應(yīng)能緩解等待的不悅。也就是說,可以先給綜觀的評(píng)論,或是讓可以更快速回覆的人來做 code review,都可以讓開發(fā)者得到快速回應(yīng)。注意,快速回應(yīng)不代表自己要中斷手上的工作;而且,快速回應(yīng)仍應(yīng)花足夠多的時(shí)間做 code review 以確保程式碼的品質(zhì) (LGTM)。 跨時(shí)區(qū)儘量在對(duì)方當(dāng)天上班時(shí)間時(shí)回覆,或是確保在他(們)隔天上班前完成 review,讓他(們)能在一早上班時(shí)收到回覆。 在評(píng)論中標(biāo)註 LGTMLGTM 表示「Looks Good To Me」,意思是審閱者大致上瞄了一下覺得可以摟-本次 CL 已過關(guān),可以合併進(jìn)入程式庫中了。 為了加快 code review 的速度,有一些狀況是審閱者就算沒有 review 過所有的程式碼,貨留下一些未解決的評(píng)論,而可以直接以 LGTM 同意通過本次 CL。以下是這些狀況…
針對(duì)以上兩種狀況,審閱者應(yīng)該要明確指出是本次 CL 的哪些部份。當(dāng)審閱者與開發(fā)者在不同時(shí)區(qū)時(shí),特別推薦使用 LGTM,不然開發(fā)者可要等一天後才會(huì)得到審閱者回覆「LGTM,接受本次 CL」。 一次收到一大包程式碼的 CL假設(shè)收到很大一包的 CL 導(dǎo)致難以確切預(yù)估完成 review 的時(shí)間,可以要求開發(fā)者拆解為多個(gè)較小的 CL。但若無法拆解,審閱者至少可以針對(duì)整體設(shè)計(jì)給予一些回饋來讓開發(fā)者做改善,因?yàn)閷忛喺弑仨氉龅讲灰钃蹰_發(fā)者接下來的工作並兼顧程式碼的品質(zhì)。 好好進(jìn)行 Code Review 能讓未來的日子更好過假設(shè)確實(shí)遵照 guideline 來做 code review,會(huì)發(fā)現(xiàn) code review 的速度會(huì)愈來愈快。開發(fā)者會(huì)讓發(fā)出的 CL 是有一定水準(zhǔn)的、能維持程式庫的健康,而能減少 code review 來回的次數(shù)和時(shí)間。審閱者也能快速回應(yīng)而無不必要的延遲。注意,不能在 code review 的標(biāo)準(zhǔn)或品質(zhì)上做妥協(xié)來加快速度,因?yàn)橐蚤L遠(yuǎn)角度來看,降低標(biāo)準(zhǔn)或品質(zhì)並不會(huì)讓 code review 的速度變快-因?yàn)樵黾蛹夹g(shù)債會(huì)讓以後的日子更難過。 緊急狀況在緊急狀況下所產(chǎn)生的 CL 該怎麼辦呢?像是要上線一個(gè)必須立刻修復(fù)的 bug…審閱者這時(shí)候要關(guān)注的是 review 的速度和程式碼的正確性,有修復(fù)問題即可 approve,然後之後要再重新 review 來給予原本應(yīng)該要給的意見。 如何給予 Code Review 的評(píng)論先講結(jié)論。
禮貌審閱者的態(tài)度必須是要有禮貌與尊重開發(fā)者的,審閱者尤其是要說出一些可能會(huì)讓人感到不開心或具有爭議的話,就必須注意是要「對(duì)事不對(duì)人」,可以針對(duì)程式碼留下評(píng)論,而非針對(duì) 開發(fā)者 給予評(píng)論。 例如…
解釋「為什麼」在評(píng)論中解釋「為什麼」,有助於開發(fā)者了解這麼做的原因以及有什麼好處。 給予指引修復(fù) CL 是開發(fā)者的責(zé)任,因此審閱者是不需要來對(duì)解法做細(xì)部的設(shè)計(jì)或幫開發(fā)者撰寫程式碼。但當(dāng)審閱者指出問題所在、提供解法的方向來讓開發(fā)者做決策時(shí),能讓開發(fā)者學(xué)習(xí)到更多東西,也能讓 code review 的過程更順利。然而,直接的指示、建議或範(fàn)例程式碼會(huì)是更有幫助的。code review 的第一要?jiǎng)?wù)是讓 CL 變得更好,第二個(gè)目標(biāo)則是讓開發(fā)者學(xué)到東西、提升技能,以期未來能花更少 code review 的時(shí)間。 如果審閱者看到 CL 中有不錯(cuò)的地方,也可以在評(píng)論中提出。例如:開發(fā)者整理了一段很亂的寫法,並且加上測試程式,或是讓審閱的人有學(xué)到什麼。這類鼓勵(lì)的評(píng)論也能讓開發(fā)者知道這麼做是好的,值得持續(xù)下去。 接受說明當(dāng)審閱者要求開發(fā)者解釋一段程式碼時(shí),通常會(huì)導(dǎo)致開發(fā)者重寫來讓這段程式碼更簡單易懂。不過有時(shí)候,開發(fā)者只會(huì)加上註解,只要這段註解不是在解釋過於複雜的程式碼即可,這樣也是可以接受的,不一定要重寫。這樣的解釋通常會(huì)希望寫在程式碼裡面,但若只是要讓審閱的人看懂(而其他開發(fā)者是可以很容易理解的),是可以只寫在 review 的工具裡面。 拖延處理 code review 的回覆開發(fā)者不同意審閱者的建議或認(rèn)為審閱者過於嚴(yán)格的時(shí)候,很可能會(huì)拖延處理 code review 的回覆。 誰才是對(duì)的?當(dāng)開發(fā)者不同意審閱者的建議時(shí),審閱者應(yīng)該要先思考一下開發(fā)者的解釋是否有道理(尤其是否可增進(jìn)程式庫的健康程度),因?yàn)殚_發(fā)者通常是最了解目前程式碼狀況的人。如果開發(fā)者的看法是有道理的,就採納吧。 但若審閱者的建議才對(duì)呢?審閱者應(yīng)該要做更進(jìn)一步的解釋,並且針對(duì)開發(fā)者所提出的疑問提供解答,也要告知這樣的改善好在哪裡。特別是當(dāng)審閱者認(rèn)為這樣的建議可以改善程式庫的健康程度,審閱者應(yīng)該要應(yīng)該要堅(jiān)持這樣的主張,儘管可能會(huì)導(dǎo)致要多做一些工作,但這樣的工作通??梢院芸焱瓿?。有時(shí)候在修改程式碼前會(huì)來來回回好幾趟來解釋審閱者的評(píng)論,注意要保持禮貌,並且讓開發(fā)者理解到審閱者有聽到開發(fā)者的建議,只是不認(rèn)同。 基本上大家保持開放心胸來討論就應(yīng)該不成問題摟? 不要擔(dān)心會(huì)讓開發(fā)者不開心審閱者有時(shí)會(huì)認(rèn)為若自己堅(jiān)持要做修改,則會(huì)讓開發(fā)者感到不開心,但其實(shí)這樣的不開心可能存在,但也只是短暫存在,之後開發(fā)者會(huì)感激審閱者對(duì)於程式碼品質(zhì)改善的建議。況且,若評(píng)論的態(tài)度是禮貌的,開發(fā)者通常也不會(huì)覺得不悅,因此審閱者應(yīng)該要認(rèn)清這種不悅的情緒通常和撰寫評(píng)論的方式或禮貌有關(guān),而非針對(duì)堅(jiān)持程式碼的品質(zhì)。 等會(huì)再處理開發(fā)者常會(huì)因?yàn)樽陨碛刑喙ぷ鞫七t(甚至是不做)某些工作,例如:清除廢棄程式碼。通常開發(fā)者希望審閱者能先專注在主要的工作上,也就是允許本次 CL,然後再下一個(gè) CL 再來解決廢棄程式碼的問題,只是開發(fā)者可能會(huì)因?yàn)楣ぷ鞯膬?yōu)先順序而導(dǎo)致這件事情都不會(huì)發(fā)生。因此,若無其他考量(例如:增加程式碼的複雜度),應(yīng)堅(jiān)持在本次 CL 進(jìn)行,而不是之後再說;若真的會(huì)有問題,則也必須開一個(gè) bug 單給自己、並在程式碼中加註 TODO 來做提醒務(wù)必要完成這個(gè)工作。 對(duì)於嚴(yán)格審核的抱怨如果審閱者過去對(duì)程式碼審核的標(biāo)準(zhǔn)是比較鬆散的,而現(xiàn)在變得嚴(yán)格,開發(fā)者可能會(huì)不適應(yīng)這樣的改變而產(chǎn)生抱怨,解法就是審閱者要加快自身審核的速度。抱怨無法馬上消失,但開發(fā)者會(huì)發(fā)現(xiàn)嚴(yán)格的審核對(duì)程式碼品質(zhì)的提升而支持這樣的做法。 若審閱者與開發(fā)者發(fā)生衝突,請(qǐng)?jiān)倩仡^閱讀這裡。 參考資料標(biāo)籤: code review 閱讀筆記
|
|