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

前言

原文是 Google 工程師 Michael Lynch 的個人博客文章:

  • mtlynch.io/human-code-r
  • mtlynch.io/human-code-r

讀了之後深有感觸,目前國內大多數公司對於 Code review 的重視程度還遠遠不夠,大多數人都把它視為一件麻煩事。即使在有 Code review 流程的團隊,也缺乏相關經驗,而且目前中文技術圈關於 Code review 的文章真的太少了,所以在這裡翻譯這篇個人認為很不錯的文章給大家看。


正文

最近,我讀了很多關於 code review 最佳實踐的文章。但我發現大多數文章都著重於教你如何找到代碼中的 bug,而幾乎完全忽略了 code review 的其他部分。比如你溝通的方式是否足夠建設性及專業呢?無所謂!只要找到所有 bug,剩下的就自生自滅去吧。

所以我獲得了一個啟示:如果這些跟代碼相關,為什麼不能以一種更浪漫的形式呢?於是,我想把我的新書介紹給開發者們,以幫助他們以及他們熱愛的生活。

我這本革命性的書將會教你一些實用的技巧,以幫助你最大限度地找到你同伴身上的所有缺點。但它不會包括以下內容:

  • 如何與你的同伴溝通,讓你們相互理解、產生共鳴
  • 如何幫助你的同伴找到他們的不足之處

這本書適合你嗎?我彷彿聽到了你喊「不不不不!」

所以,為什麼我們一定要用那種(沒人性的)方式來討論 code review 呢?

唯一的回答就是,我是在遙遠的未來讀這本書的,那個時代所有的開發者都是機器人。在那個世界裡,你的團隊小夥伴很喜歡冰冷的、生硬的、無情的評論,因為它們都是機器人,閱讀這些評論能溫暖它們的機械之心。

但我想做一個大膽的假設,你現在就想改善你們團隊的 code review,而你們的團隊成員都是活生生的人類。我還想做一個更大膽的假設,那就是 「與同事建立積極的關係」 本身就是一個目的,而不是簡單地調整某個變數,以減少 bug。在這種情況下,你的 code review 會發生怎樣的變化呢?

我的這篇文章會介紹一些技巧,讓 code review 不再僅僅是一種技術上的流程,更是一種社交性的流程。


什麼是 Code Review?

「Code Review」 這個術語實際上包含了一個很大範圍內的活動,從最簡單的讀同伴的代碼,到 20 個人正襟危坐在會議室里一行一行地剖析代碼,都可以稱為 「Code Review」。在後文里,我用這個術語來指代一個正式且書面化的過程,但不是像內部 Code Review 會議那樣重量級。

參與 review 的人有兩種,一種是作者(author),也就是代碼的編寫者和 review 的發起者;另一種是評審者(reviewer),也就是閱讀代碼,並且決定代碼是否可以合併進入團隊代碼庫的人。評審者可以有多個人,但後文里我會假設你是唯一的評審者。

在 review 開始之前,作者必須創建一個變更列表(changelist)。它含有一系列的修改,而作者想要把這些修改合併進入代碼庫。

當作者把變更列表交給評審者之後,review 就開始了。review 是一種輪轉的方式進行的,每一輪都是一次作者和評審者之間的往返:作者提交變更列表,評審者對這些變更作出反饋。每個 review 都會有一輪或多輪。

當評審者同意(approve)這些更改之後,code review 就結束了。這個時候一般會評論一句 「LGTM」,也就是 「looks good to me」 的縮寫。


這有哪些難點?

設想一下,如果一名程序員給你遞交了一份他們自認為很贊的變更列表,而你回復了一大堆問題,告訴他這份變更列表並不好,這裡就很容易讓人感到冒犯。

這就是為啥我從來不懷念 IT 行業的原因,程序員都是些很不討人喜歡的人… 要是放在航天行業,這些高估自己能力的人墳頭草都已經一米多高了.

--- Philip Greenspun,ArsDigita 聯合創始人

對於代碼的作者來說,評論或者批評他們的代碼,很容易被視為一種暗示,即他們不是一名稱職的程序員。雖然 code review 是一個很好的機會來分享知識、做一些工程上的抉擇,但如果 code review 被視為人身攻擊,那麼這些好處都不會發生。

就算上面這種情況不會發生,你也會遇到溝通的問題,把你腦子裡的想法用文字準確地表述下來是很具有挑戰性的,因為別人很容易產生誤解。代碼的作者聽不到你的語音語調,也看不到你的肢體語言,所以清楚的寫下你的反饋是一件很重要的事情。對於戒備心理很強的代碼作者而言,一句無意的 「你忘了關掉 file handle」,可以被理解為,「你竟然忘了關閉 file handle?你這個蠢豬。」


技巧

讓電腦做重複的事情

在會議和郵件的交替轟炸中,你能專註於代碼的時間實際上是很稀有的,你的精神耐受力甚至更短。讀團隊小夥伴的代碼需要大量的精力和高度的精神集中,所以不要把精力花在計算機可以代替我們做的事情上,特別是那些計算機做得更好的事情。

空格問題就是一個明顯的例子。比較一下,評審者靠肉眼找到一處縮進錯誤,然後協助代碼作者修正它,和直接使用一個代碼格式化工具,哪一個耗費的時間更少?

表格的右邊之所以啥都不需要干,是因為作者已經使用了一個自動格式化工具,在每次保存代碼時,都會自動執行。最壞的情況下,作者沒檢查代碼就提交 review 了,持續集成系統也會報錯,這樣作者就可以在評審者發現之前就修復這個問題。

在你的 code review 中找到可以被自動化的地方,下面是一些常見的點:

自動化可以讓作為評審者的你做更多有意義的事情。當你可以不需要在意某大類問題(例如 imports 的順序、源文件的命名約定),你就可以有更多的精力關注其他更有趣的問題,例如函數錯誤或者可讀性差的問題。

自動化同樣可以讓代碼的作者受益,它可以讓作者快速地在幾秒鐘(而不是幾小時)內找到一些粗心產生的低級錯誤。快速的錯誤反饋產生的修復成本也很低,因為代碼作者的腦中依然有這段代碼的上下文。另外,來自電腦的報錯相比於來自評審者的糾錯,從自尊心上講,更容易讓人接受。

你的團隊應該立刻把這樣一套自動化工具加入到 code review 的工作流中(比如 Git 的 pre-commit hook,還有 Github 的 webhook)。如果 review 需要評審者手工去做這些事情,那就喪失了很多益處。代碼的作者總是會忘記遵守某些東西,以至於你必須重複地檢查很多簡單又低級的問題,而這些問題本應是自動化工具代替你做的。


用代碼風格規範來解決代碼風格的爭議

關於代碼風格的爭吵在 code review 中是非常浪費時間的。一致的代碼風格當然是非常重要的,但 code review 的時間並不該浪費在討論圓括弧該放在哪裡。最好的做法是通過維護一份代碼風格規範來避免這裡的爭吵。

一份優秀的代碼風格規範,不僅僅定義了諸如命名規範、空格規範這些表面上的東西,同樣也應該定義如何使用語言的某些特性。例如 JavaScript 和 Perl,它們具有函數式編程的特性 —— 也就是說它們提供了多種方式來完成同一件事情。代碼規範里應該只定義一種正確的方式,這樣的話才能讓你的團隊不會一半的人用某些語言特性,而另一半的人用完全不同的其它特性。

當你有了一份風格規範後,你就再也不需要把時間浪費在討論誰的命名規範最好這種問題上了,只要按照規範來就可以。如果你的風格指南沒有針對某個特殊問題作出規定,那麼它在 review 過程中不應該被討論。如果遇到規範中沒有涵蓋的問題,並且這個問題足夠重要,那麼可以與團隊成員進行討論,然後將討論結果記錄在代碼風格規範中,這樣你們以後就不用再討論了。

選項一:使用一份已有的代碼規範

在網上搜一搜,就能找到不少已有的代碼規範,其中 Google Style Guide 是最廣為人知的。當然,如果它不適合你的話,你也可以用別的規範。使用已有的規範,你可以直接獲得收益,而不需要從零開始創建一份規範。

直接復用一份現成的規範,缺點在於,這些規範可能是為了原團隊中某些特殊需求而優化的。比如 Google 的代碼規範,對於新的語言特性十分保守,因為他們有一個巨大無比的代碼庫,這些代碼可能會運行在很多地方,從家庭路由器,到最新款的 iPhone 上。如果你所在的是只有四個人和一款產品的小型初創公司,那麼你可能會更喜歡使用最尖端的語言特性或者擴展。

選項二:漸進式地建立你自己的代碼規範

如果不想直接使用現成的代碼規範,當然可以自己創建一份。每當 code review 時產生了關於代碼風格的爭議,就把這個問題提給團隊所有成員,來決定正式的標準。達成一致後,把這個標準寫入你的代碼規範中。

我一般喜歡把我團隊的代碼風格規範以 Markdown 的格式託管在版本控制軟體之下(比如 Github pages)。這樣,對規範的任何修改都會經過一個正式的 review 流程 —— 必須有某人明確地批准修改,團隊中的任何人都有機會提出疑慮。用 Wiki 和 Google Docs 當然也是可以的。

選項三:混合式的方法

結合選項一和選項二,你可以用一份現成的代碼規範,在它的基礎上,建立你自己的代碼格式規範。比如 Chromium C++ style guide 就是個很好的例子,它建立在 Google C++ style 的基礎之上,但有它自己修改或添加的一些規則。


立刻開始 Review

code review 應該被視作高優先順序的事情。你閱讀代碼並提供反饋意見時,花點時間無所謂,但 review 必須要立刻開始 —— 最好是在幾分鐘內。

Code Review 的接力賽

一旦團隊成員提交給了你一份變更列表,這可能意味著在你的 review 完成之前,他們的工作會被阻塞住。雖然在理論上,版本控制系統可以讓代碼的作者切換到新的分支,然後把審核中的提交合併到新的分支,繼續工作。但實際上,只有很少的人能高效率地做這些事情,因為這需要同時同步三個分支(譯者註:master、review 中的分支、新分支)的變動。

當你立即開始 code review,你就創造了一個良性循環。你一輪 review 所需要花的時間,和變更列表的大小及複雜度成正相關。這就鼓勵了代碼的作者提交小範圍的變更列表,這也讓你的 review 變得更輕鬆愉悅,你的 review 也會更快,形成一個正向循環。

想像一下,你的小夥伴實現了一個新功能,需要改變 1000 行代碼。 如果他們知道你可以在大約 2 小時內查看 200 行的更改列表,則可以將其功能分解為多個變更,每個約 200 行,於是你就可以在一兩天內 review 完畢。 但是,如果你每個 review 都是拖了一天之後才開始 ,那麼就需要花費幾乎一周的時間才能做完 review。你的小夥伴當然不想就呆坐在那裡一周,因此他們就會偏向於提交更大體積的 review,比如500-600行。 這些大的 review 要花的時間更多,而且會產生質量更差的反饋意見(因為你要在腦內記住 600 行的變化而不是 200 行)。

一輪 review 的最大周期應該限於一個工作日,如果你因為某些優先順序更高的事情忙成狗了,那麼請你告訴你的小夥伴,讓他們把 review 的任務交給別人。如果每個月都有幾次這樣的情況發生,那就說明你的團隊需要減速了,這樣才能保證團隊的開發不會失去控制(譯者註:在中國就別想了)


自頂向下的方法

你在一輪 review 中提出的問題越多,代碼作者感到的壓力就會越大。具體數量的限制因人而異,但一般一輪 review 提出的問題應該限制在 20 - 50 個之內。

如果你擔心評論太多,把代碼原作者淹沒在茫茫的問題之中,那麼建議你在早期的 review 中先關注一些高層次的問題,例如重新設計類的介面,以及分解複雜函數等。直到這些問題得到解決,再去關注低層次的問題,比如變數命名,或者代碼注釋的清晰程度。

代碼原作者關注高層次問題時,一些低層次的問題可能會被忽視。把這些低層次問題暫時延後到下一輪 review,就可以避免重複檢查這些低級問題,也可以節省代碼原作者的時間。這個小技巧可以讓你在 review 過程中對應該關注的層面進行細分,幫助你和原作者以一種更加清晰、系統的方式處理更改列表。


多寫代碼示例

在理想的世界裡,代碼作者應該會很感謝他們收到的每一個 review,因為這是一個很好的學習機會,並且讓他們糾正了錯誤。但在現實中,有諸多因素可能會導致作者負面地看待這些 review,並且反感你對他們代碼的評論。也許他們正面臨著壓力,要在最後期限之前完成任務,所以除了即刻批准以外的任何事情都會被視為一種阻礙。 也許你們之間沒有太多的合作經驗,所以他們不相信你的反饋是好意的。

這有一個好方法,可以在 review 過程中舒緩作者的心情,那就是在 review 期間找機會送給他們禮物。所有開發者都喜歡接受的禮物是什麼?那當然是,代碼示例。

你可以通過寫一些示例代碼來減輕作者的負擔,以展現出你作為評審者的慷慨大度。

比如說,你有一個同事並不是很熟悉 Python 的列表推導特性,他給你發了如下的代碼:

urls = []nfor path in paths:n url = https://n url += domainn url += pathn urls.append(url)n

作為回復,一句 「我們能不能用列表推導來簡化這兒的代碼?」 可能會讓他們感到惱怒,因為他們或許需要花 20 分鐘來搜索他們之前從沒用過的東西。

但如果收到的評論是像下面這樣,他們應該會很高興:

可以考慮這樣簡化代碼:

urls = [https:// + domain + path for path in paths]

這個小技巧不僅限於單行代碼。我會經常創建自己的分支來向原作者展示大量的概念,比如拆解一個大的函數,或者添加單元測試來覆蓋額外的邊界情況。

這個小技巧會讓你得到明確的、無爭議的改進。在上面的示例中,很少有人會反對將代碼行數減少83%。相比之下,如果你寫了個冗長的例子來展示你個人品味上覺得 「更好」 的示例(例如,代碼風格),這會使你看起來更一意孤行,而不是開明大方。

當然示例代碼也不能寫得太多了,如果你為原作者寫了幾乎覆蓋整個變更列表的示例代碼,那就表示你不認為他們有能力編寫自己的代碼。


不要說「你」

這聽起來有些奇怪,但請相信我說的:在 code review 中,不要使用 「你」 這個詞。

你在 review 中所做的評論應該是基於 「什麼使得代碼更好」,而不是 「誰提出了這個想法」。你的小夥伴在他們的變更列表上花費了大量的精力,他們當然會為他們所做的工作感到自豪,所以當收到批評時,自然會產生戒備心理。

你應該用一種最不會產生戒備心理的方式來評論代碼。要記住你批評的是代碼,而不是代碼的作者。當代碼的作者在評論里看到 「你」 這個詞的時候,會把注意力從代碼上轉移到他們自己身上。這會加劇他們抗拒你的評論的可能性。

比如說下面這個無惡意的評論:

你把 successfully 拼錯了

作者可能會把它讀成兩種意思:

  • 含義一:嘿,好兄弟!你把 successfully 拼錯了,但我認為你這麼聰明,這應該只是一處小粗心吧!
  • 含義二:你把 successfully 拼錯了!白痴!

相比之下,如果你的評論里沒有提及 「你」 這個詞:

sucessfully -> successfully

這條評論就只是簡單地糾正了拼寫錯誤,沒有包含任何對於作者的評價。

幸運的是,在評論中去掉 「你」 這個詞非常容易。

選項一:用 「我們」 代替 「你」

你能不能把這個變數名寫得更清晰一點?比如 seconds_remaining

修改之後:

我們能不能把這個變數名寫得更清晰一點?比如 seconds_remaining

「我們」 這個詞強調了團隊對於代碼的責任。代碼的作者可能未來會去別的公司,你也可能會,但這裡的代碼會被它所屬的團隊一直維護著。當然,用 「我們」 這個詞聽起來有些愚蠢,因為這顯然是你作為一名評審者,要求作者做的事情,但愚蠢總比指責更好。

選項二:移除句子的主語

避免使用 「你」 的另一個方法是移除句子的主語:

建議使用更清晰的變數名,比如 seconds_remaining

當然被動語態也是可以的。雖然我在寫技術文章時盡量避免使用被動語態,但它在 「你」 這個詞的問題上確實有用處:

這個變數應該使用更清晰的名字,比如 seconds_remaining

還有一種方法是把它化為一個問題:

要把這個變數名換成更清晰的嗎?比如 seconds_remaining


使用請求的語氣,而不是命令

Code review 比日常的交流需要更多的精力,因為很容易把討論變成個人觀點的碰撞。你總是期望評審者能在評論中保持禮貌,但奇怪的是,他們總是和期望相反。日常工作中,大多數人都不會對同事說:「把訂書機拿給我,然後給我倒一杯蘇打水過來。」 但 review 過程中卻經常看到類似這樣的評論:「把這個 class 放到另一個文件里。」

這樣的語句經常會讓你的評論令人惱怒。你的評論應該使用請求或者建議的語氣,而不是命令。

比較下面這兩種語氣的區別:

大多數人都喜歡完全掌控他們的工作,使用請求的語氣可以讓他們有自主的感覺。

另外,請求的語氣也讓作者更容易禮貌地推辭你的評論,或許他們是出於某些原因,考慮過後才寫下的代碼。如果你的語氣是命令式的,那麼作者的任何推辭和解釋都會變成直接的不服從行為。如果你用的是請求或者提問的語氣,那麼作者可以簡單地回復你。

比較下面這兩種情況:

看,當你的語氣變成請求式之後,是不是交流少了很多火氣呢?


評論應該基於原則,而不是觀點

當你寫下一條評論,要記得同時寫下要做什麼以及為什麼要做。說 「我們應該把這個 class 切分成兩個」 是不太好的,更好的說法是 「現在這個 class 負責下載並且解析文件。根據單一職責原則,我們應該把它切分成兩個 class,一個負責下載,一個負責解析。」

你的評論應該是基於原則的,這樣才可以讓 review 是建設性的。當你有一個明確的理由為什麼要這樣做時,比如 「我們應該把這個方法私有化,以減少公共方法的數量」,作者一般都不會簡單地回復 「不,我更喜歡現在這樣」。即使他們這樣簡單地回復了,這樣的回復也會看起來很傻,因為你都已經說明理由了,而他們只是因為個人偏好而拒絕你的理由。

軟體開發既是一門藝術,也是一門科學。有些時候,即使有了既定的原則,你也不能清楚地證明一段代碼是錯誤,因為有時代碼只是醜陋或不直觀而已。在這些情況下,請詳細解釋一下為什麼,並保持客觀。比如如果你說 「覺得這很難理解」,這就是一個客觀的陳述。但如果你說 「這裡寫得亂七八糟」,這就是一種價值判斷,是仁者見仁智者見智的。

另外,在提供支持性材料的時候,儘可能以鏈接的形式附在後面。團隊的代碼風格規範是最好的,當然你也可以發一個語言或庫文檔的鏈接,高贊數的 StackOverflow 答案也可以。但是要知道的是,離權威性文檔越遠,材料就越無力。


第二部分

如果你喜歡這篇文章,還可以去看它的第二部分,著重於介紹如何讓 Code Review 順利完成,而不是各種碰壁。第二篇文章會介紹以下技巧:

  • 如何處理超大的 Code review
  • 恰當地稱讚對方
  • 限制 Review 的範圍
  • 如何緩解僵局

推薦閱讀:

景觀設計做方案時團隊應如何分工?
技術經理時間經常被下屬的業務問題或者技術問題佔用,這個現象合不合理,如果不合理如何改善?
跟領導關於工作問題相處不好,該怎麼辦?
從一名員工向管理者轉變,應該做哪些方面的準備?應該注意些什麼問題?

TAG:codereview | 软件开发 | 团队管理 |