原文是 Google 工程師 Michael Lynch 的我的博客文章:html
讀了以後深有感觸,目前國內大多數公司對於 Code review 的重視程度還遠遠不夠,大多數人都把它視爲一件麻煩事。即便在有 Code review 流程的團隊,也缺少相關經驗,並且目前中文技術圈關於 Code review 的文章真的太少了,因此在這裏翻譯這篇我的認爲很不錯的文章給你們看。python
最近,我讀了不少關於 code review 最佳實踐的文章。但我發現大多數文章都着重於教你如何找到代碼中的 bug,而幾乎徹底忽略了 code review 的其餘部分。好比你溝通的方式是否足夠建設性及專業呢?無所謂!只要找到全部 bug,剩下的就自生自滅去吧。c++
因此我得到了一個啓示:若是這些跟代碼相關,爲何不能以一種更浪漫的形式呢?因而,我想把個人新書介紹給開發者們,以幫助他們以及他們熱愛的生活。git
我這本革命性的書將會教你一些實用的技巧,以幫助你最大限度地找到你同伴身上的全部缺點。但它不會包括如下內容:程序員
這本書適合你嗎?我彷彿聽到了你喊「不不不不!」github
因此,爲何咱們必定要用那種(沒人性的)方式來討論 code review 呢?web
惟一的回答就是,我是在遙遠的將來讀這本書的,那個時代全部的開發者都是機器人。在那個世界裏,你的團隊小夥伴很喜歡冰冷的、生硬的、無情的評論,由於它們都是機器人,閱讀這些評論能溫暖它們的機械之心。編程
但我想作一個大膽的假設,你如今就想改善大家團隊的 code review,而大家的團隊成員都是活生生的人類。我還想作一個更大膽的假設,那就是「與同事創建積極的關係」自己就是一個目的,而不是簡單地調整某個變量,以減小 bug。在這種狀況下,你的 code review 會發生怎樣的變化呢?bash
個人這篇文章會介紹一些技巧,讓 code review 再也不僅僅是一種技術上的流程,更是一種社交性的流程。markdown
「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 的基礎之上,但有它本身修改或添加的一些規則。
code review 應該被視做高優先級的事情。你閱讀代碼並提供反饋意見時,花點時間無所謂,但 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' 拼錯了
做者可能會把它讀成兩種意思:
相比之下,若是你的評論裏沒有說起 「你」 這個詞:
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 順利完成,而不是各類碰壁。第二篇文章會介紹如下技巧: