這是我文章的後半部分,關於如何在 Code review 中進行良好的溝通,避免陷入一些潛在的陷阱。這裡,我會著重於介紹一些技巧,讓你的 Code review 能夠順利完成,避免磕磕碰碰。

在第一篇文章中,我介紹了很多基礎的東西,所以我更推薦從那裡開始讀。當然如果你沒有耐心,那麼這裡用一句話概括一些:一個好的程式碼評審者,不僅需要找出程式碼中的 bug,還需要提供認真負責的反饋來讓團隊夥伴得到能力上的提升。

我最糟糕的一次 Code review

我最糟糕的一次 Code review 經歷,是和一個叫 Mallory 的前同事有關。她早我幾年加入公司,但最近才轉到我的團隊來。

Review 的經歷

當我收到 Mallory 的第一份變更列表,裡面的程式碼是很粗糙的。她之前完全沒有寫過 Python 程式碼,而且她的程式碼是基於一個我維護的笨重的老系統上開發的。

我很盡職地記錄下了所有我找到的問題,一共有 59 個。按照其它 Review 文章的標準來看,我做得很不錯。我找到了

如此多

的錯誤,所以我肯定是個優秀的評審者。

過了幾天後,Mallory 給了我一份更新過的變更列表,已經針對我之前的 Review 修改過了。她修復了一些簡單的問題,比如拼寫錯誤、變數重新命名等等,但她拒絕修改任何更高層次的問題,比如她的程式碼對於錯誤的輸入會產生不確定的行為,還有她的一個函式嵌套了 6 層邏輯。她拒絕修改這些東西,輕蔑地解釋說,這些東西不值得花工作時間去修復。

我看到這些,心情有些惱怒,所以又寫了一輪新的評論。我的語氣很專業,但是不可避免的有些火藥味。“你能解釋一下,為什麼我們需要對於錯誤的輸入會產生不確定的行為嗎?”正如你所猜的那樣,Mallory 的回覆比之前更固執了。

【譯】如何用人類的方式進行 Code Review(二)

惡性迴圈

一週後的星期二,Mallory 和我依然停留在第四輪相同的 Review 上。我前一天晚上提交了我的新一輪評論,但實際上我是故意拖了一天才提交的,因為當她讀這些評論時,我不想跟她呆在一個屋子裡。

整個早上,我一直覺得胃不舒服,因為我恐懼又要開始新一輪 Review。當我吃完午飯回來時,發現 Mallory 已經提交了新的程式碼,但她已經不在位置上了,我估計她也不想呆在我身邊看著我審查這些更新。

我讀了程式碼之後,我的心劇烈地跳動,因為真的被她惹怒了。我立刻開始奮力敲擊我的鍵盤,指出她既沒有按照我的要求作出修改,也沒有用任何理由說服我批准這個變更列表。

我們就這樣毅種迴圈(誤)了整整三個星期,而程式碼幾乎沒怎麼改動過。

介入

幸運的是,我們團隊裡最高級別的工程師,Bob,幫我們打破了這個迴圈。他剛剛休完長假回來,驚訝地發現我們兩個正在把 Code review 相互扔來扔去。他意識到這是一個僵局,所以請求我們讓他接管這個 Review,我們都同意了。

Bob 在最開始時,先讓 Mallory 建立了幾個新的變更列表,把我們倆之前爭吵從來沒有提到過的兩個小型庫分隔開,每個大概 30 - 50 行。Mallory 做完之後,Bob 立刻批准了。

然後,Bob 回到主要的變更列表上來,這個列表現在已經被裁剪成 200 行左右的程式碼。他給了一些小建議,Mallory 響應修改了。再然後,他批准了這個變更列表。

Bob 的整個 Review 就花了兩天。

溝通很重要

你或許已經發現,這兒產生的衝突其實並不是關於程式碼的。Code review 裡提出的問題都是合情合理的,它們也明顯可以被溝通能力強的團隊成員解決。

這是一個很不愉快的經歷,但我很高興能回想起來,因為它讓我重新思考了我的 review 方法是否恰當,並從中找到可以改進的地方。

下面,我會介紹一些技巧,以避免類似的不愉快經歷。然後我會回到 Mallory 這個例子上,解釋為什麼我之前的方法是不好的,而為什麼 Bob 做得那麼好。

一些技巧

旨在把程式碼改進一到兩個級別

即使你的團隊夥伴,理論上,想找到任何機會來讓他們的程式碼變得更好,但他們的耐心也依然是有限的。當你經過一輪又一輪的 review 之後,依然不肯批准變更,他們的心情會變得很沮喪,因為你總是在不停地想各種方法來改進他們的程式碼。

我個人會把程式碼分幾個級別,從 A 到 F。當我收到一份評分為 D 的變更列表,我會嘗試幫助作者把它改進到 C 或者 B-。不是完美的,但足夠好了。

當然,把一份 D 評分的程式碼改進到 A+ 在理論上是可能的,但它可能會需要八輪 review。最後,程式碼作者會怨恨你,並且未來再也不給你任何 review 的機會了。

你可能會想,“如果我批准了 C 等級的程式碼,那最後程式碼庫裡不就全都是 C 等級的程式碼了嗎?” 其實不然,我發現,如果我幫助團隊成員從 D 改進到 C,那麼他們的下一份變更列表就會從 C 等級開始。幾個月後,他們給我的 review 都是從 B 等級開始了,這些改進之後就會變成 A 等級。

當然有一個特例,那就是 F 等級,這個等級是保留給一些寫得太爛的程式碼的,一般是功能不正確,或者寫得太錯綜複雜,以致於你無法判斷程式碼的正確性。你駁回它的唯一理由應該是,它經過了幾輪 review 之後,依然沒有任何改進。這時,請參考下面的關於僵局的部分。

對於相同的問題,有限度的反饋

當你發現了作者很多相同的問題,不需要把每一處都標明,你肯定不想把同樣的評論重複 25 次,程式碼的作者也不想讀 25 次相同的評論。

對於同樣的問題,只需要重複註明 2- 3 次,然後剩下的,就直接評論說讓作者修復類似的問題就好了,而不是去註明每一處問題。

【譯】如何用人類的方式進行 Code Review(二)

遵守 review 的範圍

有一個我經常見到的反模式,那就是評審者會評論變更列表附近的一些程式碼,並要求作者修改它們。作者修改後,評審者通常會覺得程式碼確實更好了,但和別處的程式碼有一些不一致,於是又要求進一步的更改,以及再進一步的更改。最後讓變更列表擴充套件得很大,包括了很多不相關的東西。

如果一隻飢餓的小老鼠出現在你的家門口,你可能會想給他一塊餅乾。如果你給他一塊餅乾,它又會想要一杯牛奶。然後它會想要一面鏡子,以確保它的鬍子上沒有粘上牛奶,然後它會要一把剪刀給自己剪剪鬍子。。。

—— Laura Joffe Numeroff, If You Give a Mouse a Cookie

所以有一條準則:

如果變更列表沒有涉及到這一行,那它就是超出 review 範圍的。

下面就是一個例子:

【譯】如何用人類的方式進行 Code Review(二)

即使你失眠了一整晚,被這個魔術數字和荒唐的變數名所困擾,它也是超出範圍的。即使寫下這一行程式碼的人就是本次 review 的作者,它也是超出範圍的。如果它真的非常糟糕,那也請你提一個 bug,或者提交你的修復,但不要把它放到本次 review 的範疇中來。

當然有個例外,那就是當變更列表影響了周圍的程式碼時,比如:

【譯】如何用人類的方式進行 Code Review(二)

這個例子裡,需要讓作者把

ValidateAndSerialize

重新命名為

Serialize

。雖然這超出了變更列表的範圍,但它會導致不正確的命名。

當我沒發現問題,但發現了範圍外的某個很容易修復的問題時,我會不遵守這個規則。在這種情況下,我會明確地表示,作者可以完全無視它。

【譯】如何用人類的方式進行 Code Review(二)

嘗試切分體積很大的 review

當你收到了一個約 400 行程式碼的變更列表,你應該鼓勵作者把它切分到更小的塊。超過得越多,你就越應該打回這個變更列表。我個人拒絕 review 任何超過 1000 行的變更列表。

【譯】如何用人類的方式進行 Code Review(二)

作者對於 “切分變更列表” 這件事可能會有些怨言,因為這是個很乏味的任務,所以作為評審者的我們最好為作者劃出要切分邊界,以減輕他們的負擔。最簡單的一種情況是,變更列表涉及到多個檔案,這時便可以按檔案切分為多個更小的變更列表。當然也會有更復雜一些的情況,這時也許需要找到抽象層次比較低的函式或者類,然後要求作者把它們移動到另一個變更列表裡,等另一個變更列表合併之後,再回來看現在的這一個。

當代碼寫得很糟糕時,就更應該要求切分。因為這時 review 的難度隨著程式碼的長度呈指數級增長。Review 多個 300 行的變更,比 review 單個 600 行的變更,要好得多。

真誠的表揚

大多數評審者都只關注程式碼中的

錯誤

,但忘記了 review 也是一個促進積極行為的好機會

比如,你正在 review 的這個作者是個文件寫得很掙扎的人,但你卻看到了一條清晰、簡潔的註釋,這時請表揚他一下。當你告訴他們什麼做得對時,他們會進步得更快,而不是等著他們犯錯才告訴他們。

【譯】如何用人類的方式進行 Code Review(二)

你不需要刻意地去表揚作者,每當我看到變更列表中有任何亮點,我都會告訴作者:

“我沒想到還有這個 API,它用處很大!”

“這是個很優雅的解決方法,我之前還沒想到可以這樣做。”

“這個函式重構得很不錯,它現在簡單得多了。”

如果作者是個最近才加入團隊的萌新,他們在 code review 的時候可能會感到緊張和焦慮。為了緩解這種情緒,你需要真誠地表揚他們,證明你是支援他們的同伴,而不是殘酷無情的程式碼守門員。

當剩下的都是些小問題時,直接批准

有些評審者會有一個錯誤的觀念,他們總會一直不肯給出批准,直到他們看到每一條評論都得到了修復。這增加了不必要的工作,也浪費了作者和評審者的時間。

當有下面這些情況時,可以直接批准變更:

你沒有更多的評論想寫了

剩下的評論都是些無關緊要的小問題,比如變數命名、拼寫錯誤

剩下的評論都是 “可選的”(記得明確地標明這些評論不是一定要修復,這樣你的小夥伴才不會認為一定要改掉這裡才能讓你批准)

我之前看到過有些評審者一直不肯給出批准,因為作者在最後的評論後就沒再有任何迴應了。請不要這樣做,這會讓作者覺得你認為他們連加一個標點符號都應該被監視著。

當還剩一些很重要的問題沒修改時,直接給出批准可能會很危險。根據我的經驗,大約有 5% 的機率會發生這樣的事,要麼作者誤解了最後一輪評論的意思,要麼他完全沒看到。為了解決這種情況,我會去檢查一遍要批准的變更。如果真的是因為罕見的溝通不暢,我要麼會繼續跟進作者,要麼會自己建立一個新的變更來修復問題。在 5% 的情況下增加少量的工作,要好過於,在另外 95% 的情況下加入更多不必要的精力和拖延。

主動處理僵持狀態

在 code review 中,最糟糕的情況就是僵持:如果沒有進一步更改的話,你不想批准這個變更列表,而程式碼的作者拒絕修改它。

下面這些指標,表示你正在走向僵持狀態:

討論的語氣變得越來越有敵意和火藥味

你的每一輪評論都不是自上而下的了

你回覆的評論異常之多

【譯】如何用人類的方式進行 Code Review(二)

說出來

直接面對面交流或者透過網路影片。文字交流最後會讓你忘了你對話的是一個真正的人,很容易讓你想象你的同伴是一個來自無能與固執之地的人。面對面的交流將會打破這個魔咒。

考慮設計階段的審查(design review)

可能會進入僵持狀態的 code review 可能在更早的時候就有一些徵兆了。你們現在是不是在一些事情上爭吵,而這些事情本應該在設計審查時討論的?你們有設計審查嗎?

如果分歧的根源是高層次的設計問題,那就應該是由更大的團隊來權衡利弊,而不是交給 code review 的兩個人。你應該和作者在 review 中討論,同時開放給團隊其他成員,以設計審查的形式讓他們也進來討論。

讓步或者升級

你和你的同伴在 review 中僵持得越久,就越有害於你們之間的關係。如果問題一直沒有得到解決,那麼你最好選擇讓步,或者把問題升級。

學會衡量給出批准的成本。當你總是接受低質量程式碼時,你的軟體質量當然不可能好,但當你和你的團隊夥伴痛苦地工作,以至於你們無法在一起愉快地合作時,軟體質量同樣也不會有任何提高。這個時候應該想想,如果你批准了變更列表,結果會有多糟?這些程式碼會破壞重要的資料嗎?它是一個後臺程序,並且只有當最壞的情況發生時才需要開發人員對它進行 debug 嗎?如果更偏向於後者,那麼可以考慮批准變更,以便讓你和你的團隊夥伴繼續工作。

如果你不想做出讓步,請告訴作者,將這裡的爭議升級到團隊的管理者或者技術負責人那裡,把 review 重新分配給另外的評審者。即使之後得出的結果和你之前的意見相悖,也要接受它。繼續固執下去只會導致更不好的結果,讓你看起來很不專業。

脫離僵局之後

一次棘手的 review 中的爭吵,真正的重點很可能並不是程式碼,而是人和人的關係。如果你陷入了僵局或者即將陷入僵局,並且不去解決那些潛在的衝突,那麼這種情況會一直髮生下去。

與你的上級討論這種情況

如果團隊中產生了衝突,你的上級應該會知道。也許是因為程式碼作者本來就是個很難合作的人,也許是因為你們只是用了相互瞭解的方式在解決當前的狀況。一個好的上級會幫你們解決這些情況。

休息一下

如果可能的話,相互之間停止程式碼評論幾個星期,直到兩邊都冷靜下來。

學會解決衝突

有一本書叫 “Crucial Conversations” 很不錯。雖然它給出的建議似乎都是些常識性的東西,但當你作為一個旁觀者來看衝突時,就會發現它分析衝突的方法真的很棒。

重新思考我最糟糕的那次 Code review

還記得我和 Mallory 的那次 code review 嗎?為什麼我的 review 花了整整三週,並且成效甚微,而 Bob 僅花了兩天就搞定了?

我做錯了什麼

這是 Mallory 在我們團隊的第一次 review。我之前並沒有考慮到她可能會感覺自己受到評判,產生戒備心。我應該在最開始的時候給出一些高層次的評論,讓她不會因為大量的評論而感到挫折。

我應該更好地向她表明,我的工作不是去阻礙她的工作,而是幫助她變得更好。我應該給出更多的程式碼示例,以及在她的變更列表中給出更多的積極因素。

我的自尊心也過多地影響了這次 review。要知道我花了一整年的時間,才把這個老系統修復到健康狀態,突然有了一個新人開始在這個系統上做些事情,而她就不再需要考擔心這些我曾經擔憂的問題了嗎?我認為這是一種冒犯,也就是這種態度產生了副作用。我應該在所有的評論上都保持一個客觀的態度。

最後一點,我讓這個僵局持續太久了。在幾輪 review 過後,我們都應該很清楚,我們沒有任何有意義的進展。這時我應該主動做出改變,比如主動當面交流,以解決深層次的衝突,或者把它升級到我們的上級那裡。

Bob 哪些地方是對的

Bob 第一步把 review 切分的做法非常有效。這個 review 已經花費了痛苦的三週時間,忽然,其中的兩塊程式碼被合併了。這讓 Mallory 和 Bob 都感覺到激勵,因為它產生了向前的動力。儘管剩下的程式碼裡依然存在問題,但它變成了一個更小的,更好管理的變更列表。

Bob 沒有嘗試在 review 的過程中把程式碼改造成完美無缺的,他可能已經意識到那些程式碼裡我覺得無法忍受的問題,但他也同樣意識到 Mallory 會待在我們團隊一段時間。對於某些特殊情況的靈活處理,讓他可以在長時間內幫助 Mallory 提升質量。

結論

在我發表這篇文章的前半部分之後,有些讀者對我鼓勵的溝通風格產生了一些質疑。有人覺得它太屈尊卑微了,有人覺得這種風格太委婉了,會產生誤解。

這樣的反饋是合情合理的。對於同樣的一條評論,有人可能會覺得它很粗魯無禮,而有的人卻會覺得它很簡潔高效。

當你在 review 程式碼時,你會面臨很多選擇:該關注什麼,怎麼寫反饋,什麼時候給出批准。對於這些選擇,你是否遵循我的建議其實不重要,只要你記得有這些選擇就好。

沒有人可以給你一個完美的 code review 指南。什麼是最好的技巧,取決於程式碼作者的性格、你和他們的關係還有你們團隊的文化。不斷思考你的 code review 會產出什麼結果。當你遇到任何緊張形勢時,先退一步思考為什麼會發生。關注你 review 的質量。如果你覺得程式碼質量很難提升到符合你的標準,應該思考 review 的那些過程阻礙了你,以及如何解決它們。

最後祝好運,希望你能用人類的方式進行 code review。