如何讓你的程式碼審查者愛上你
原文由 Michael Lynch 于 發布,訂閱此部落格
談到程式碼審查,大家往往只關注審查者。但實際上,寫程式碼的開發者對審查的重要性,一點也不亞於讀程式碼的人。關於如何為審查準備程式碼,幾乎沒有什麼指引,因此許多作者純粹出於無知就把這個過程搞砸了。
本文將介紹當你身為作者時,參與程式碼審查的最佳實務。事實上,讀完這篇文章後,你送審程式碼的技巧會好到讓你的審查者真的愛上你。
可是我不想讓審查者愛上我
他們就是會愛上你。接受吧。從來沒有人在臨終時抱怨有太多人愛上自己。
為何要精進你的程式碼審查?
精進程式碼審查的技巧,能幫助你的審查者、你的團隊,最重要的是:你自己。
學得更快:如果你妥善準備好變更清單(changelist),就能將審查者的注意力引導到有助於你成長的地方,而不是枯燥的風格違規上。當你表現出對建設性批評的重視,審查者就會提供更好的回饋。
讓他人變得更好:你的程式碼審查技巧會為同事樹立榜樣。有效的作者實務會潛移默化地影響隊友,這會讓他們把程式碼送來給你審查時,你的工作變得更輕鬆。
減少團隊衝突:程式碼審查是常見的摩擦來源。用審慎、有意識的態度面對它們,就能減少爭執。
黃金法則:珍惜審查者的時間
這個建議聽起來顯而易見,但我經常看到作者把審查者當成專屬的品質保證技術人員。這些作者完全不花心思揪出自己的錯誤,也不設法讓變更清單更易於審查。
你的隊友每天上班時,專注力都是有限的。如果他們把其中一部分分給你,那就代表他們無法把這些時間用在自己的工作上。最大化他們時間的價值,才是公平的做法。
當雙方互相信任時,審查的品質會大幅提升。當審查者可以信賴你會認真看待他們的回饋,他們就會投入更多心力。如果你把審查者視為必須克服的障礙,就會限制他們能為你帶來的價值。
技巧
- 先自己審查一次程式碼
- 寫出清晰的變更清單描述
- 把簡單的事自動化
- 用程式碼本身來回答問題
- 嚴格限縮變更範圍
- 區分功能性與非功能性的變更
- 拆解過大的變更清單
- 優雅地回應批評
- 當審查者弄錯時保持耐心
- 明確地溝通你的回應
- 巧妙地索取欠缺的資訊
- 平手時一律讓審查者獲勝
- 縮短審查來回之間的等待時間
1. 先自己審查一次程式碼
在把程式碼送給隊友之前,自己先讀一遍。不要只是檢查錯誤——想像你是第一次讀到這段程式碼。有哪些地方可能會讓人困惑?
我發現寫完程式碼後先休息一下再審查很有幫助。很多人習慣在一天結束前急著送出變更,但那正是最容易忽略粗心錯誤的時候。不妨等到隔天早上,用全新的眼光再看一次變更清單,然後再交給隊友。

盡可能採用審查者的環境。使用他們會看到的同一個 diff 檢視。在 diff 檢視中,比在平常的原始碼編輯器裡更容易發現愚蠢的錯誤。
別指望自己能做到完美。難免會送出帶有忘記刪除的除錯程式碼、或夾帶了本想排除的檔案的變更清單。這些錯誤並非世界末日,但值得留意。注意自己犯錯的模式,並思考建立一套機制來預防。如果這種情況太常發生,就會讓審查者覺得你不珍惜他們的時間。
2. 寫出清晰的變更清單描述
在上一份工作中,我透過開發者導師計畫定期與一位資深工程師會面。第一次會面前,他請我帶一份自己寫的設計文件。當我把文件遞給他時,我口頭解釋了專案內容以及它如何與團隊目標一致。我的導師皺起眉頭,直截了當地說:「你剛剛告訴我的這些,應該都要寫在設計文件的第一頁。」
他說得對。我寫設計文件時,想的是隊友會怎麼讀,卻沒考慮到其他讀者。除了直屬隊友之外,更廣泛的讀者還包括合作團隊、導師以及升遷審查委員會。他們都應該能夠理解這份文件。自從那次談話後,我總會思考該如何為自己的工作提供脈絡說明。
你的變更清單描述應該總結讀者所需的任何背景知識。你寫描述時心裡可能有特定的程式碼審查者,但他們未必擁有你想像的那些脈絡。此外,你的其他隊友也可能需要閱讀這份變更清單,而未來回顧變更紀錄的讀者也應該能理解你的用意。
好的變更清單描述會在高層次上說明這次變更達成了什麼,以及你為何要做這項變更。
若想更深入了解如何寫出優秀的變更清單描述,請參閱我的另一篇文章:〈如何寫出有用的 Commit 訊息〉。
3. 把簡單的事自動化
如果你靠審查者來告訴你大括號放錯行,或是你的變更弄壞了自動化測試,那你就是在浪費他們的時間。

自動化測試應該是你團隊標準工作流程的一部分。審查應該在所有自動化檢查都在持續整合環境中通過之後才開始。
如果你的團隊觀念嚴重偏差、拒絕投資持續整合,那就自己動手自動化這些檢查。在你的開發環境中加入 git pre-commit hooks、linter 和 formatter,確保每次提交時,程式碼都能遵守適當的規範並保留預期的行為。
4. 用程式碼本身來回答問題
這張圖哪裡不對勁?

作者幫我搞懂了這個函式,但下一個讀到它的人呢?難道他們要去翻遍變更紀錄、把每一場程式碼審查的討論都讀過一遍嗎?更糟的是,作者直接走到我座位旁當面解釋,這不僅打斷我的專注,也確保了其他人永遠無法取得這些資訊。
當審查者對程式碼的運作方式表示困惑時,解法不是只向那一個人解釋。你需要向所有人解釋清楚。

回答他人疑問的最佳方式,是重構程式碼以消除困惑。你能重新命名或重組邏輯,讓它更清晰嗎?程式碼註解是可以接受的解法,但嚴格來說還是不如能自然自我說明的程式碼。
5. 嚴格限縮變更範圍
範圍蔓延是程式碼審查中常見的反模式。一位開發者原本要修邏輯上的錯誤,卻在過程中注意到 UI 上的瑕疵。「既然都來了,」他心想,「就順手把那個也修一修吧。」但這麼一來就把事情搞混了。審查者必須搞清楚哪些變更屬於目標 A,哪些屬於目標 B。
最好的變更清單只做一件事。變更越小、越簡單,審查者就越容易在腦中掌握所有脈絡。將不相關的變更解耦,也能讓你在不同隊友之間平行進行審查,縮短變更的處理時間。
6. 區分功能性與非功能性的變更
限縮範圍的推論,就是要區分功能性與非功能性的變更。
對程式碼審查不熟悉的開發者經常違反這條規則。他們只改了兩行程式碼,然後編輯器自動把整個檔案重新格式化。開發者要不是沒意識到自己做了什麼,就是覺得新的格式比較好。結果他們送出的,是夾雜在數百行非功能性空白變更中的兩行功能性變更。

你能找出藏在這份變更清單空白雜訊中的功能性變更嗎?
混亂的變更清單對審查者來說是極大的不尊重。純空白的變更很好審查。兩行的變更也很好審查。但在茫茫的空白變更海中迷失的兩行功能性變更,卻既乏味又令人抓狂。
開發者在重構時也常常不當地混雜變更。我很樂見隊友重構程式碼,但我討厭他們一邊重構一邊改變程式碼的行為。

這份變更清單只做了一處行為上的變更,但重構的變更卻將其掩蓋了。
如果一段程式碼既需要重構又需要改變行為,應該分成兩到三個變更清單來完成:
- 加入測試來驗證現有行為(如果還沒有的話)。
- 在測試程式碼保持不變的情況下,重構正式程式碼。
- 在正式程式碼中改變行為,並同步更新測試。
在步驟 2 中保持自動化測試不動,你就能向審查者證明你的重構保留了原有行為。到了步驟 3,審查者就不必再費心將行為變更與重構變更拆解開來,因為你已經事先將它們分離了。
7. 拆解過大的變更清單
過大的變更清單可說是範圍蔓延的醜陋近親。假設一位開發者發現為了引入功能 X,必須修改現有函式庫 A 和 B 的語意。如果只是一小組變更,那還無妨,但這類蔓延式的修改一多,就會讓變更清單變得無比龐大。
變更清單的複雜度會隨著所觸及的程式碼行數呈指數成長。當我的變更超過 400 行正式程式碼時,我就會在請求審查前尋找拆解的機會。
與其一次改完全部,你能否先修改相依項目,再在下一個變更清單中加入新功能?如果你現在先加入一半的功能、另一半留到下一個變更清單,程式碼庫是否仍能維持在合理的狀態?
要把程式碼拆開、找出一個能獨立運作且合乎邏輯的子集,過程很繁瑣,但這能帶來更好的回饋,也能減輕審查者的負擔。
8. 優雅地回應批評
毀掉一場程式碼審查最快的方法,就是把回饋當成針對個人。這很具挑戰性,因為許多開發者以自己的作品為榮,並將其視為自我的延伸。如果審查者又毫不客氣地把回饋寫得像是人身攻擊,那就更難了。
身為作者,你最終能控制自己對回饋的反應。把審查者的意見當成對程式碼的客觀討論,而不是對你個人價值的評斷。防衛性的回應只會讓事情更糟。
我試著把所有意見都當成有幫助的學習機會。當審查者在我的程式碼中揪出令人尷尬的錯誤時,我的第一反應是想找藉口。但我會克制自己,轉而稱讚審查者的細心。

當審查者揪出你程式碼中細微的錯誤時,記得表達感謝。
令人意外的是,當審查者能發現你程式碼中細微的缺陷時,其實是個好兆頭。這表示你把變更清單包裝得很好。少了那些明顯的問題——像是糟糕的格式和令人困惑的命名——審查者就能深入專注於邏輯與設計,從而提供更有價值的回饋。
9. 當審查者弄錯時保持耐心
審查者有時就是會大錯特錯。就像你可能不小心寫出有錯的程式碼一樣,審查者也可能誤解正確的程式碼。
許多開發者對審查者的錯誤會產生防衛心。他們覺得有人用根本不正確的批評來指責自己的程式碼,是一種冒犯。
即使審查者弄錯了,那仍然是個警訊。如果他們會誤讀,其他人是否也會犯同樣的錯誤?讀者是否必須付出超乎尋常的仔細程度,才能確信某個錯誤並不存在?

當審查者犯錯時,抗拒想要證明他是錯的衝動。
試著重構程式碼,或加上註解,讓程式碼變得更加顯然正確。如果困惑源自冷僻的語言特性,就改用非專家也能理解的方式來重寫程式碼。
10. 明確地溝通你的回應
我經常遇到這樣的情況:我給了對方意見,他們更新了程式碼來處理部分回饋,卻沒有寫任何回覆。現在就陷入了曖昧不明的狀態。他們是漏看了我其他的意見,還是仍在處理中?如果我開始新一輪審查,可能會在一個尚未完成的變更清單上白費時間。如果我等待,又可能造成僵局——雙方都在等對方先動作。
在團隊中建立慣例,讓任何時刻都很清楚是誰「拿著接力棒」。要嘛是作者正在修改,要嘛是審查者正在撰寫回饋。絕不應該出現因為沒人知道該誰行動而導致流程停擺的情況。你可以透過在變更清單層級的留言,清楚標示何時將主導權交還給對方,輕鬆做到這點。

在變更清單上留言,明確告知何時將主導權交還給審查者。
對於每個需要處理的意見,都要明確回應以確認你已處理。有些程式碼審查工具允許你將留言標示為已解決。否則,就遵循一個簡單的慣例,例如對每則意見回覆「完成」。如果你不同意該意見,也請有禮貌地說明你為何不採取行動。

像 Reviewable 和 Gerrit 這類程式碼審查工具,提供了讓作者將特定意見標示為已解決的機制。
根據審查者付出的心力來調整你的回應。如果他們寫了詳細的意見來幫助你學習新東西,不要只是標示完成。要用心回應,表達對他們付出的感謝。
11. 巧妙地索取欠缺的資訊
有時程式碼審查的意見會留下太多解讀空間。當你收到像「這個函式很讓人困惑」這樣的留言時,你可能會想,究竟「困惑」是什麼意思?是函式太長?名稱不清楚?還是需要更多文件?
很長一段時間,我一直苦於如何在不顯得防衛的情況下釐清模糊的意見。我的直覺是問:「是哪裡讓人困惑?」但這聽起來很不耐煩。
有一次,我不小心給隊友發了一則含糊的意見,而他的回應方式讓我覺得非常巧妙、化解了防備:
怎麼做會比較有幫助呢?
我很喜歡這個回應,因為它展現出不帶防衛心、樂於接受批評的態度。每當審查者給我不夠明確的回饋時,我總會用類似「怎麼做會比較有幫助呢?」的方式來回應。
另一個有用的技巧是猜測審查者的用意,並根據這個假設主動修改程式碼。對於像「這很讓人困惑」這樣的意見,再仔細看一次自己的程式碼。通常總有某些地方可以做得更清晰。主動修改能向審查者傳達你樂於做出改變的訊息,即使那不是他們心中所想的修改。
12. 平手時一律讓審查者獲勝
在網球中,當你不確定對手的發球是否出界時,你會給予對方有利的判定。程式碼審查也應該有類似的期待。

美國網球協會要求球員在進行線審判定時,應給予對手有利的判定。
有些關於程式碼的決定純屬個人品味問題。如果審查者認為你那個 8 行的函式拆成兩個 5 行的函式會更好,你們雙方都沒有客觀上的「正確」。哪個版本比較好,只是觀點之別。
當審查者提出建議,而你們雙方都有大致相當的理由支持各自的立場時,就順從審查者吧。在你們兩人之中,他們更能體會初次閱讀這段程式碼是什麼感覺。
13. 縮短審查來回之間的等待時間
幾個月前,一位使用者對我維護的開源專案貢獻了一項小變更。我在幾小時內就給了回饋,但他們隨即消失了。幾天後我再去查看,仍然沒有回應。
六週後,這位神祕的開發者才再次出現並提交修改。雖然我感謝他們的努力,但審查來回之間的長時間延遲讓我的工作量加倍。我不僅得重讀他們的程式碼,還得重讀自己的回饋來喚回對討論的記憶。如果他們在一兩天內就追蹤回覆,我就不需要做這些額外的工作。

六週的停頓算是極端案例,但我經常在隊友之間看到漫長而不必要的延遲。有人送出變更清單進行審查、收到回饋後,卻因為被其他任務分心,就把它擱置一週。
除了為了恢復脈絡而損失的時間,半完成的變更清單也會增加複雜度。它們讓每個人都更難追蹤哪些已經合併、哪些仍在進行中。半完成的變更清單越多,合併衝突就越多,而沒有人喜歡處理那些。
一旦你把程式碼送出去,將審查推向完成就應該是你的最高優先事項。你這端的延遲會浪費審查者的時間,並增加整個團隊的複雜度。
結論
當你準備下一份要送審的變更清單時,想想那些你能掌控的因素,並利用它們來有效地引導審查。在參與審查的過程中,留意那些會阻礙進度或浪費心力的模式。
記住黃金法則:珍惜審查者的時間。當你讓審查者能專注於程式碼中有趣的部分時,他們就能產生高品質的回饋。如果你要他們去解開糾結的程式碼或糾正簡單的錯誤,你們雙方都會受害。
最後,要用心溝通。簡單的誤解或欠考慮的留言輕易就能讓審查脫軌。在批評他人作品時情緒很容易高漲,因此要留意那些可能讓審查者感到被攻擊或不被尊重的陷阱。
恭喜!如果你讀到這裡,你現在已經是送審的專家了。你的審查者很可能已經愛上你,所以好好對待他們吧。
延伸閱讀
- 如何像人一樣做程式碼審查:既然你已經從作者的角度學到了有效的實務,現在就來學習當你身為審查者時,如何精進你的程式碼審查。
插圖由 Loraine Yow 繪製。由 Samantha Mason 編輯。
隨機一篇部落格
留言
登入後參與討論