譯者前言
原文是 Google 工程師 Michael Lynch 的個人部落格文章:
- https://mtlynch.io/human-code-reviews-1/
- https://mtlynch.io/human-code-reviews-2/
讀了之後深有感觸,目前國內大多數公司對於 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 中找到可以被自動化的地方,下面是一些常見的點:
任務 | 自動化解決方案 |
---|---|
確認程式碼可以構建無誤 | 持續整合系統,例如 Travis 或者 CircleCI |
確認程式碼可以通過自動化測試 | 持續整合系統,例如 Travis 或者 CircleCI |
確認程式碼的空格和縮排符合團隊規範 | 程式碼格式化工具,例如 ClangFormat (C/C++ formatter) 或者 gofmt (Go formatter) |
找出無用的 imports 或者無用的變數 | 程式碼格式檢查工具,例如 pyflakes (Python linter) 或者 JSLint (JavaScript linter) |
自動化可以讓作為評審者的你做更多有意義的事情。當你可以不需要在意某大類問題(例如 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 = []
for path in paths:
url = 'https://'
url += domain
url += path
urls.append(url)
複製程式碼
作為回覆,一句 “我們能不能用列表推導來簡化這兒的程式碼?” 可能會讓他們感到惱怒,因為他們或許需要花 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 放到另一個檔案裡。”
這樣的語句經常會讓你的評論令人惱怒。你的評論應該使用請求或者建議的語氣,而不是命令。
比較下面這兩種語氣的區別:
命令 | 請求 |
---|---|
把 Foo class 移到一個單獨的檔案裡 | 我們能不能把 Foo class 移到一個單獨的檔案裡? |
大多數人都喜歡完全掌控他們的工作,使用請求的語氣可以讓他們有自主的感覺。
另外,請求的語氣也讓作者更容易禮貌地推辭你的評論,或許他們是出於某些原因,考慮過後才寫下的程式碼。如果你的語氣是命令式的,那麼作者的任何推辭和解釋都會變成直接的不服從行為。如果你用的是請求或者提問的語氣,那麼作者可以簡單地回覆你。
比較下面這兩種情況:
命令(對抗的語氣) | 請求(相互合作的語氣) |
---|---|
評審者:把 Foo class 放到單獨的檔案裡 | 評審者:我們能不能把 Foo class 放到單獨的檔案裡? |
作者:我拒絕,因為那樣的話它就會遠離 Bar class。客戶端裡幾乎都是一起用這兩個 class 的。 | 作者:可以啊,但如果這樣的話,它就會遠離 Bar class,客戶端幾乎總是一起使用這兩個 class 的,你覺得該怎麼做? |
看,當你的語氣變成請求式之後,是不是交流少了很多火氣呢?
評論應該基於原則,而不是觀點
當你寫下一條評論,要記得同時寫下要做什麼以及為什麼要做。說 “我們應該把這個 class 切分成兩個” 是不太好的,更好的說法是 “現在這個 class 負責下載並且解析檔案。根據單一職責原則,我們應該把它切分成兩個 class,一個負責下載,一個負責解析。”
你的評論應該是基於原則的,這樣才可以讓 review 是建設性的。當你有一個明確的理由為什麼要這樣做時,比如 “我們應該把這個方法私有化,以減少公共方法的數量”,作者一般都不會簡單地回覆 “不,我更喜歡現在這樣”。即使他們這樣簡單地回覆了,這樣的回覆也會看起來很傻,因為你都已經說明理由了,而他們只是因為個人偏好而拒絕你的理由。
軟體開發既是一門藝術,也是一門科學。有些時候,即使有了既定的原則,你也不能清楚地證明一段程式碼是錯誤,因為有時程式碼只是醜陋或不直觀而已。在這些情況下,請詳細解釋一下為什麼,並保持客觀。比如如果你說 “我覺得這很難理解”,這就是一個客觀的陳述。但如果你說 “這裡寫得亂七八糟”,這就是一種價值判斷,是仁者見仁智者見智的。
另外,在提供支援性材料的時候,儘可能以連結的形式附在後面。團隊的程式碼風格規範是最好的,當然你也可以發一個語言或庫文件的連結,高贊數的 StackOverflow 答案也可以。但是要知道的是,離權威性文件越遠,材料就越無力。
第二部分
如果你喜歡這篇文章,還可以去看它的第二部分,著重於介紹如何讓 Code Review 順利完成,而不是各種碰壁。第二篇文章會介紹以下技巧:
- 如何處理超大的 Code review
- 恰當地稱讚對方
- 限制 Review 的範圍
- 如何緩解僵局