How to Make Your Code Reviewer Fall in Love with You

Michael Lynch

如何讓你的程式碼審查者愛上你

原文由 Michael Lynch 發布,訂閱此部落格

談到程式碼審查,大家往往只關注審查者。但實際上,寫程式碼的開發者對審查的重要性,一點也不亞於讀程式碼的人。關於如何為審查準備程式碼,幾乎沒有什麼指引,因此許多作者純粹出於無知就把這個過程搞砸了。

本文將介紹當你身為作者時,參與程式碼審查的最佳實務。事實上,讀完這篇文章後,你送審程式碼的技巧會好到讓你的審查者真的愛上你

可是我不想讓審查者愛上我

他們就是會愛上你。接受吧。從來沒有人在臨終時抱怨有太多人愛上自己。

為何要精進你的程式碼審查?

精進程式碼審查的技巧,能幫助你的審查者、你的團隊,最重要的是:你自己。

  • 學得更快:如果你妥善準備好變更清單(changelist),就能將審查者的注意力引導到有助於你成長的地方,而不是枯燥的風格違規上。當你表現出對建設性批評的重視,審查者就會提供更好的回饋。

  • 讓他人變得更好:你的程式碼審查技巧會為同事樹立榜樣。有效的作者實務會潛移默化地影響隊友,這會讓他們把程式碼送來給你審查時,你的工作變得更輕鬆。

  • 減少團隊衝突:程式碼審查是常見的摩擦來源。用審慎、有意識的態度面對它們,就能減少爭執。

黃金法則:珍惜審查者的時間

這個建議聽起來顯而易見,但我經常看到作者把審查者當成專屬的品質保證技術人員。這些作者完全不花心思揪出自己的錯誤,也不設法讓變更清單更易於審查。

你的隊友每天上班時,專注力都是有限的。如果他們把其中一部分分給你,那就代表他們無法把這些時間用在自己的工作上。最大化他們時間的價值,才是公平的做法。

當雙方互相信任時,審查的品質會大幅提升。當審查者可以信賴你會認真看待他們的回饋,他們就會投入更多心力。如果你把審查者視為必須克服的障礙,就會限制他們能為你帶來的價值。

技巧

  1. 先自己審查一次程式碼
  2. 寫出清晰的變更清單描述
  3. 把簡單的事自動化
  4. 用程式碼本身來回答問題
  5. 嚴格限縮變更範圍
  6. 區分功能性與非功能性的變更
  7. 拆解過大的變更清單
  8. 優雅地回應批評
  9. 當審查者弄錯時保持耐心
  10. 明確地溝通你的回應
  11. 巧妙地索取欠缺的資訊
  12. 平手時一律讓審查者獲勝
  13. 縮短審查來回之間的等待時間

1. 先自己審查一次程式碼

在把程式碼送給隊友之前,自己先讀一遍。不要只是檢查錯誤——想像你是第一次讀到這段程式碼。有哪些地方可能會讓人困惑?

我發現寫完程式碼後先休息一下再審查很有幫助。很多人習慣在一天結束前急著送出變更,但那正是最容易忽略粗心錯誤的時候。不妨等到隔天早上,用全新的眼光再看一次變更清單,然後再交給隊友。

第一格:狗狗看著變更清單問道:『是哪個白痴寫的?』第二格:PR 標題為『將 cron 工作同步至月相週期』,描述寫著『我加入了這段同步邏輯,以確保大自然與我們的 ETL 流程和諧一致。口述但未校閱』,署名是同一隻狗狗。第三格:狗狗一臉尷尬。

盡可能採用審查者的環境。使用他們會看到的同一個 diff 檢視。在 diff 檢視中,比在平常的原始碼編輯器裡更容易發現愚蠢的錯誤。

別指望自己能做到完美。難免會送出帶有忘記刪除的除錯程式碼、或夾帶了本想排除的檔案的變更清單。這些錯誤並非世界末日,但值得留意。注意自己犯錯的模式,並思考建立一套機制來預防。如果這種情況太常發生,就會讓審查者覺得你不珍惜他們的時間。

2. 寫出清晰的變更清單描述

在上一份工作中,我透過開發者導師計畫定期與一位資深工程師會面。第一次會面前,他請我帶一份自己寫的設計文件。當我把文件遞給他時,我口頭解釋了專案內容以及它如何與團隊目標一致。我的導師皺起眉頭,直截了當地說:「你剛剛告訴我的這些,應該都要寫在設計文件的第一頁。」

他說得對。我寫設計文件時,想的是隊友會怎麼讀,卻沒考慮到其他讀者。除了直屬隊友之外,更廣泛的讀者還包括合作團隊、導師以及升遷審查委員會。他們都應該能夠理解這份文件。自從那次談話後,我總會思考該如何為自己的工作提供脈絡說明。

你的變更清單描述應該總結讀者所需的任何背景知識。你寫描述時心裡可能有特定的程式碼審查者,但他們未必擁有你想像的那些脈絡。此外,你的其他隊友也可能需要閱讀這份變更清單,而未來回顧變更紀錄的讀者也應該能理解你的用意。

好的變更清單描述會在高層次上說明這次變更達成了什麼,以及你為何要做這項變更。

若想更深入了解如何寫出優秀的變更清單描述,請參閱我的另一篇文章:〈如何寫出有用的 Commit 訊息〉

3. 把簡單的事自動化

如果你靠審查者來告訴你大括號放錯行,或是你的變更弄壞了自動化測試,那你就是在浪費他們的時間。

狗狗打斷貓咪的工作,問道:『你可以幫我確認程式碼語法是否正確嗎?我本來想問編譯器,但我不想浪費它的時間。』

自動化測試應該是你團隊標準工作流程的一部分。審查應該在所有自動化檢查都在持續整合環境中通過之後才開始。

如果你的團隊觀念嚴重偏差、拒絕投資持續整合,那就自己動手自動化這些檢查。在你的開發環境中加入 git pre-commit hooks、linter 和 formatter,確保每次提交時,程式碼都能遵守適當的規範並保留預期的行為。

4. 用程式碼本身來回答問題

這張圖哪裡不對勁?

mtlynch:我不太理解這個函式的用途。doggo:喔,這是為了防止呼叫端傳入一個缺少 frombobulate 實作的 Frombobulator。

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

當審查者對程式碼的運作方式表示困惑時,解法不是只向那一個人解釋。你需要向所有人解釋清楚。

狗狗:喂?貓咪:你六年前寫 bill.py 的時候,為什麼把 t 設成 6?狗狗:很高興你打來!那是因為營業稅是 6%。貓咪:原來如此!狗狗:這是溝通實作選擇的好方法。貓咪:微笑

回答他人疑問的最佳方式,是重構程式碼以消除困惑。你能重新命名或重組邏輯,讓它更清晰嗎?程式碼註解是可以接受的解法,但嚴格來說還是不如能自然自我說明的程式碼。

5. 嚴格限縮變更範圍

範圍蔓延是程式碼審查中常見的反模式。一位開發者原本要修邏輯上的錯誤,卻在過程中注意到 UI 上的瑕疵。「既然都來了,」他心想,「就順手把那個也修一修吧。」但這麼一來就把事情搞混了。審查者必須搞清楚哪些變更屬於目標 A,哪些屬於目標 B。

最好的變更清單只做一件事。變更越小、越簡單,審查者就越容易在腦中掌握所有脈絡。將不相關的變更解耦,也能讓你在不同隊友之間平行進行審查,縮短變更的處理時間。

6. 區分功能性與非功能性的變更

限縮範圍的推論,就是要區分功能性與非功能性的變更。

對程式碼審查不熟悉的開發者經常違反這條規則。他們只改了兩行程式碼,然後編輯器自動把整個檔案重新格式化。開發者要不是沒意識到自己做了什麼,就是覺得新的格式比較好。結果他們送出的,是夾雜在數百行非功能性空白變更中的兩行功能性變更。

邏輯變更被空白變更掩蓋的變更清單

你能找出藏在這份變更清單空白雜訊中的功能性變更嗎?

混亂的變更清單對審查者來說是極大的不尊重。純空白的變更很好審查。兩行的變更也很好審查。但在茫茫的空白變更海中迷失的兩行功能性變更,卻既乏味又令人抓狂。

開發者在重構時也常常不當地混雜變更。我很樂見隊友重構程式碼,但我討厭他們一邊重構一邊改變程式碼的行為。

邏輯變更被重構變更所掩蓋的變更清單

這份變更清單只做了一處行為上的變更,但重構的變更卻將其掩蓋了。

如果一段程式碼既需要重構需要改變行為,應該分成兩到三個變更清單來完成:

  1. 加入測試來驗證現有行為(如果還沒有的話)。
  2. 在測試程式碼保持不變的情況下,重構正式程式碼。
  3. 在正式程式碼中改變行為,並同步更新測試。

在步驟 2 中保持自動化測試不動,你就能向審查者證明你的重構保留了原有行為。到了步驟 3,審查者就不必再費心將行為變更與重構變更拆解開來,因為你已經事先將它們分離了。

7. 拆解過大的變更清單

過大的變更清單可說是範圍蔓延的醜陋近親。假設一位開發者發現為了引入功能 X,必須修改現有函式庫 A 和 B 的語意。如果只是一小組變更,那還無妨,但這類蔓延式的修改一多,就會讓變更清單變得無比龐大。

變更清單的複雜度會隨著所觸及的程式碼行數呈指數成長。當我的變更超過 400 行正式程式碼時,我就會在請求審查前尋找拆解的機會。

與其一次改完全部,你能否先修改相依項目,再在下一個變更清單中加入新功能?如果你現在先加入一半的功能、另一半留到下一個變更清單,程式碼庫是否仍能維持在合理的狀態?

要把程式碼拆開、找出一個能獨立運作且合乎邏輯的子集,過程很繁瑣,但這能帶來更好的回饋,也能減輕審查者的負擔。

8. 優雅地回應批評

毀掉一場程式碼審查最快的方法,就是把回饋當成針對個人。這很具挑戰性,因為許多開發者以自己的作品為榮,並將其視為自我的延伸。如果審查者又毫不客氣地把回饋寫得像是人身攻擊,那就更難了。

身為作者,你最終能控制自己對回饋的反應。把審查者的意見當成對程式碼的客觀討論,而不是對你個人價值的評斷。防衛性的回應只會讓事情更糟。

我試著把所有意見都當成有幫助的學習機會。當審查者在我的程式碼中揪出令人尷尬的錯誤時,我的第一反應是想找藉口。但我會克制自己,轉而稱讚審查者的細心。

兩位開發者正在討論變更清單。doggo:這個在 1900 年的一月和二月其實會失效。mtlynch:哇,好眼力!

當審查者揪出你程式碼中細微的錯誤時,記得表達感謝。

令人意外的是,當審查者能發現你程式碼中細微的缺陷時,其實是個兆頭。這表示你把變更清單包裝得很好。少了那些明顯的問題——像是糟糕的格式和令人困惑的命名——審查者就能深入專注於邏輯與設計,從而提供更有價值的回饋。

9. 當審查者弄錯時保持耐心

審查者有時就是會大錯特錯。就像你可能不小心寫出有錯的程式碼一樣,審查者也可能誤解正確的程式碼。

許多開發者對審查者的錯誤會產生防衛心。他們覺得有人用根本不正確的批評來指責自己的程式碼,是一種冒犯。

即使審查者弄錯了,那仍然是個警訊。如果他們會誤讀,其他人是否也會犯同樣的錯誤?讀者是否必須付出超乎尋常的仔細程度,才能確信某個錯誤並不存在?

兩位開發者在程式碼審查中爭論。mtlynch:這裡有緩衝區溢位,因為我們從未驗證在 name 中配置的記憶體是否足夠容納 newNameLen 個字元。doggo:在我的程式碼裡?不可能!建構子會呼叫 PurchaseHats,而它又會呼叫 CheckWeather,如果緩衝區長度不正確,它就會回傳錯誤。你最好先把整個 20 萬行的程式碼庫完整讀過一遍,再來考慮我有可能犯錯這件事。

當審查者犯錯時,抗拒想要證明他是錯的衝動。

試著重構程式碼,或加上註解,讓程式碼變得更加顯然正確。如果困惑源自冷僻的語言特性,就改用非專家也能理解的方式來重寫程式碼。

10. 明確地溝通你的回應

我經常遇到這樣的情況:我給了對方意見,他們更新了程式碼來處理部分回饋,卻沒有寫任何回覆。現在就陷入了曖昧不明的狀態。他們是漏看了我其他的意見,還是仍在處理中?如果我開始新一輪審查,可能會在一個尚未完成的變更清單上白費時間。如果我等待,又可能造成僵局——雙方都在等對方先動作。

在團隊中建立慣例,讓任何時刻都很清楚是誰「拿著接力棒」。要嘛是作者正在修改,要嘛是審查者正在撰寫回饋。絕不應該出現因為沒人知道該誰行動而導致流程停擺的情況。你可以透過在變更清單層級的留言,清楚標示何時將主導權交還給對方,輕鬆做到這點。

作者留言『已更新!請再看一下。』的螢幕截圖

在變更清單上留言,明確告知何時將主導權交還給審查者。

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

Reviewable 介面顯示選項:討論中、已滿意、阻擋中與處理中。『已滿意』表示你認為自己已處理了審查者的意見。

ReviewableGerrit 這類程式碼審查工具,提供了讓作者將特定意見標示為已解決的機制。

根據審查者付出的心力來調整你的回應。如果他們寫了詳細的意見來幫助你學習新東西,不要只是標示完成。要用心回應,表達對他們付出的感謝。

11. 巧妙地索取欠缺的資訊

有時程式碼審查的意見會留下太多解讀空間。當你收到像「這個函式很讓人困惑」這樣的留言時,你可能會想,究竟「困惑」是什麼意思?是函式太長?名稱不清楚?還是需要更多文件?

很長一段時間,我一直苦於如何在不顯得防衛的情況下釐清模糊的意見。我的直覺是問:「是哪裡讓人困惑?」但這聽起來很不耐煩。

有一次,我不小心給隊友發了一則含糊的意見,而他的回應方式讓我覺得非常巧妙、化解了防備:

怎麼做會比較有幫助呢?

我很喜歡這個回應,因為它展現出不帶防衛心、樂於接受批評的態度。每當審查者給我不夠明確的回饋時,我總會用類似「怎麼做會比較有幫助呢?」的方式來回應。

另一個有用的技巧是猜測審查者的用意,並根據這個假設主動修改程式碼。對於像「這很讓人困惑」這樣的意見,再仔細看一次自己的程式碼。通常總有某些地方可以做得更清晰。主動修改能向審查者傳達你樂於做出改變的訊息,即使那不是他們心中所想的修改。

12. 平手時一律讓審查者獲勝

在網球中,當你不確定對手的發球是否出界時,你會給予對方有利的判定。程式碼審查也應該有類似的期待。

一位球員為了在線審判定上恪守誠實,經常會讓一顆可能出界、或太晚才發現其實已出界的球繼續比賽。即便如此,用這種方式比賽,整體體驗還是好得多。

美國網球協會要求球員在進行線審判定時,應給予對手有利的判定

有些關於程式碼的決定純屬個人品味問題。如果審查者認為你那個 8 行的函式拆成兩個 5 行的函式會更好,你們雙方都沒有客觀上的「正確」。哪個版本比較好,只是觀點之別。

當審查者提出建議,而你們雙方都有大致相當的理由支持各自的立場時,就順從審查者吧。在你們兩人之中,他們更能體會初次閱讀這段程式碼是什麼感覺。

13. 縮短審查來回之間的等待時間

幾個月前,一位使用者對我維護的開源專案貢獻了一項小變更。我在幾小時內就給了回饋,但他們隨即消失了。幾天後我再去查看,仍然沒有回應。

六週後,這位神祕的開發者才再次出現並提交修改。雖然我感謝他們的努力,但審查來回之間的長時間延遲讓我的工作量加倍。我不僅得重讀他們的程式碼,還得重讀自己的回饋來喚回對討論的記憶。如果他們在一兩天內就追蹤回覆,我就不需要做這些額外的工作。

審查者記憶與審查延遲關係圖,顯示當審查來回之間延遲過長時所浪費的心力。

六週的停頓算是極端案例,但我經常在隊友之間看到漫長而不必要的延遲。有人送出變更清單進行審查、收到回饋後,卻因為被其他任務分心,就把它擱置一週。

除了為了恢復脈絡而損失的時間,半完成的變更清單也會增加複雜度。它們讓每個人都更難追蹤哪些已經合併、哪些仍在進行中。半完成的變更清單越多,合併衝突就越多,而沒有人喜歡處理那些。

一旦你把程式碼送出去,將審查推向完成就應該是你的最高優先事項。你這端的延遲會浪費審查者的時間,並增加整個團隊的複雜度。

結論

當你準備下一份要送審的變更清單時,想想那些你能掌控的因素,並利用它們來有效地引導審查。在參與審查的過程中,留意那些會阻礙進度或浪費心力的模式。

記住黃金法則:珍惜審查者的時間。當你讓審查者能專注於程式碼中有趣的部分時,他們就能產生高品質的回饋。如果你要他們去解開糾結的程式碼或糾正簡單的錯誤,你們雙方都會受害。

最後,要用心溝通。簡單的誤解或欠考慮的留言輕易就能讓審查脫軌。在批評他人作品時情緒很容易高漲,因此要留意那些可能讓審查者感到被攻擊或不被尊重的陷阱。

恭喜!如果你讀到這裡,你現在已經是送審的專家了。你的審查者很可能已經愛上你,所以好好對待他們吧。

延伸閱讀

  • 如何像人一樣做程式碼審查:既然你已經從作者的角度學到了有效的實務,現在就來學習當你身為審查者時,如何精進你的程式碼審查。

插圖由 Loraine Yow 繪製。由 Samantha Mason 編輯。

本文章由 muse-spark-1.2-contributor 進行翻譯

留言