當前位置: 妍妍網 > 碼農

Code Review 時,曾被我忽視的 3 件重要小事

2024-04-22碼農

來源: piglei

Code Review(程式碼評審)是一種流行的軟體開發實踐。透過在程式碼合入主分支前引入人工評審,能有效促進成員間的知識交流,提升軟體品質。

我以評審者的身份參與過大量程式碼評審。在評審一份程式碼時,有些事項長期處在我的關註榜頭部,比如設計是否考慮到了邊界情況、程式碼是否有合理的單測覆蓋。也有一些事項,因看似無關痛癢一直未引起足夠重視,直到最近,我才漸漸發現它們的重要性。

以下是曾被我忽視的 3 件重要的小事。

1. 命名

小女孩千尋誤入湯婆婆為神明開設的浴場。為了留在浴場內工作,千尋與湯婆婆簽訂了一份協定,但協定並非重點,重點是另一件看似無關緊要的小事——湯婆婆給千尋改了個名:從「千尋」改為「千」。一旦失去了原本的名字,人們便失去了逃離浴場所在的異世界的能力,甘心永世被湯婆婆所奴役。

——電影【千與千尋】

程式設計師們對「命名」的關註程度似乎呈一個「倒 U 形」曲線。缺乏經驗時,對命名的關註度很低,程式碼中充斥著各類不準確、不精確的名字,無法有效描述各種抽象概念。

下面這段程式碼中的命名就存在不少問題:

defget_var(u):"""獲取環境變量列表""" data1 = UserVarsManager.get(u) data2 = SiteVarsManager.get(u.site)return data1 + data2

隨著經驗逐漸增加,大家對命名的關註度逐步提升。計畫中的名字開始變得更具有描述性,含糊不清的名字漸漸絕跡。名字至少不會成為他人理解程式碼時的屏障。

這個階段,程式碼會逐漸演變成像是這樣:

deflist_environment_vars(user):"""獲取環境變量列表""" items_user = UserVarsManager.get(user) items_site = SiteVarsManager.get(user.site)return items_user + items_site

在絕大多數評審中,這絕對算是一份合格的程式碼,至少不大可能因為命名應發爭議。

自此之後,大部份程式設計師們對命名的關註度進入「倒 U 形」曲線的後半段: 不再如從前那般關註命名,名字只要有一定描述性,不造成歧義就足夠。 我也曾經是其中一員。

但不應在這個階段停留太久,作為程式碼評審人,我們應該不斷提升自己對於名字的敏感度。比方說,對於前面那份程式碼,也許應該提出以下評審建議:

deflist_environment_vars(user):# 1"""獲取環境變量列表""" items_user = UserVarsManager.get(user) # 2 items_site = SiteVarsManager.get(user.site)return items_user + items_site

評論 1: 計畫中對於「環境變量」的統一縮寫是 env_variables /env_vars ,此處應保持一致,使用 list_env_variables list_env_vars 評論 2: UserVarsManager.get 的命名可最佳化,因為 Manager 是一個「萬金油」名詞,雖然放在各種場景下都不違和,但也是以損失名字(等同於「職責」)的精確指向性為代價,此處可考慮改用一個更精確的名字,比如: UserEnvVarsRetriever.get(user) SiteVarsManager 同理。

雖然只是兩處小改進,但是積少成多。

每一次程式碼評審,必定涉及到許多新名字。但名字並非生來平等,不是所有名字都值得我們花費時間,應當盡量把關註點聚焦在那些最常被使用、最靠近使用者的名字上,比如 URL 路徑的資源名、資料庫模型與欄位名、工具函式(類方法)名,等等。

此外,與業務直接相關的領域詞匯重要程度極高。評審時,每一個關鍵的領域詞匯都值得仔細斟酌、反復推敲。舉個例子,開發一個影評功能,」使用者評分「、「媒體評分」、「平均分」分別該用哪些名字表示?你絕不會想要在一個檔裏看到 movie_score ,在另一個檔裏看到 movie_rating

命名這件小事,雖然看似不起眼,但計畫規模越大、所跨越的時間維度越長,在名字品質上的細微差別就越容易累加出不可估量的巨大影響。

2. 指引性註釋

夏洛已經在網上織出了光彩照人四個大字,威爾伯站在金色的陽光裏,真是光彩照人。自從蜘蛛開始扶助它,它就盡力活得跟它的名聲相襯。夏洛的網說它是王牌豬,威爾伯盡力讓自己看上去是只王牌豬;夏洛的網說它了不起,威爾伯盡力讓自己看上去了不起;現在網上說它光彩照人,它盡力讓自己光彩照人。

——【夏洛的網】

關於註釋,我向來信奉 Bob 大叔在 【程式碼整潔之道】 [1] 裏的觀點: 「註釋的恰當用法是彌補我們在用程式碼表達意圖時遭遇的失敗。」 這就是說,好程式碼應該總是能清晰說明自身意圖,無需註釋再來畫蛇添足,註釋只應該被用來描述那些程式碼之外的資訊,比如解釋「為什麽」。

正因如此,註釋總是應該被謹慎使用。假如一段程式碼很難理解,第一反應不應該是補註釋,而是應該去追求用一種更易理解的方式重寫它。

但隨著時間的推移,我漸漸意識到,事情不能一概而論。「指引性註釋」,或者說常被人們詬病為「近乎復述程式碼意圖」的描述性文字,也有著不可替代的重要作用。

Redis 的作者 antirez 就是「指引性註釋」的忠實擁護者,他曾寫過 一篇文章 [2] 詳細分析過指引性註釋在 Redis 計畫中的套用。下面這段程式碼摘自 Redis 源碼,裏面就有不少「指引性註釋」:

/* Call the node callback if any, and replace the node pointer * if the callback returns true. */if (it->node_cb && it->node_cb(&it->node))memcpy(cp,&it->node,sizeof(it->node));/* For "next" step, stop every time we find a key along the * way, since the key is lexicographically smaller compared to * what follows in the sub-children. */if (it->node->iskey) { it->data = raxGetData(it->node);return1; }

在這段程式碼中,兩段註釋並未提供任何在程式碼之外的新資訊。所以,好處是什麽?

最直觀的好處,就是這些註釋讓程式碼變得更容易理解了,它們極大地降低了人們閱讀程式碼時所需付出的心智成本。同樣一份程式碼,在缺少指引性註釋的情況下,完全理解它的行為可能得花費 10 分鐘,而有了註釋的幫助,時間也許就能縮短到 5 分鐘甚至更短。

當新開發者加入計畫時,這些指引性註釋也能助力他們更快上手。

正因如此,在評審一份程式碼時,我常常會在一段復雜的程式碼邏輯上評論: 「Nit:考慮增加一小段指引性註釋,幫助理清程式碼行為。」(Nit=nitpick,表示「雞蛋裏挑骨頭」式的並不強烈要求修改的意見)

此外,如果一段程式碼曾在評審過程中引發過一些深度討論,那麽那些討論內容,也許很適合被二次加工後,作為指引性註釋加入程式碼中。對於理解程式碼來說,它們有時有奇效。

不過,在追求「指引性註釋」的路上,也要避免踩入以下幾個陷阱:

簡單復述程式碼 :指引性註釋雖然是一種幫助理解程式碼的輔助性文字,但絕不能只是復述程式碼而已,簡單來說,你可以這麽理解兩者在傳遞資訊方面的風格差異:程式碼是一本厚厚的權威科學教材,指引性註釋則是一小冊面向中學生的科學啟蒙讀物 追求「註釋率」 :不要在「程式碼註釋率」指標上設定硬性要求,指引性文字也需要講究品質,盲目追求數量只會適得其反 不註重時效性 :過時的註釋比程式碼危害更大,要及時修改或刪除已經過期的指引性註釋

總而言之,你可以把指引性註釋當成有針對性的程式碼「教學文本」。審閱程式碼時,如果你發現一段邏輯理解起來很吃力,而程式碼本身也沒有太多最佳化空間,請不要遲疑,勇敢表達出你對於「教學文本」的需求吧!

3. 溝通方式

「我因為魯思和莎拉不得不離開我們而痛苦萬分。而令我感覺更加痛苦的是我當時以為自己是完全孤立無援的。」

「說真的,肯頓小姐……」我端起那個我用來放使用過的瓷器的托盤。「對那樣的解雇我自然是極不贊同的。我還以為那是不言自明的。」

——【長日將盡】

時至今日,仍有許多人認為軟體開發是一種單打獨鬥的工作。一位程式設計師撿起一塊鍵盤,就能源源不斷地產出程式碼,根本不需要其他人。但事實是,程式設計師單打獨鬥的黃金時代早已過去,現代軟體開發已演變成一種多人參與的協作事務。正因如此,程式設計師的日常充斥著各類溝通工作,參與程式碼評審正是其中之一。

在程式碼評審時,評審者的工作內容似乎一句話就可簡單概括: 指出他人程式碼中的不足。 這聽起來易如反掌,對不對?我曾經正是這麽以為,所謂評審,只要做完下面的「123」即可:

1. 找出所有可最佳化的點(有事說事!) 2. 等送出者完成改動,或在討論後確認維持原狀(就事論事!) 3. 合並程式碼(大功告成!)

而現實總是和理想相去甚遠,程式碼評審很少會像上面這樣順利。因為一旦涉及到人與人之間的溝通,尤其其中一方還在給另外一方「挑毛病」,事情又能簡單到哪兒去呢?

人類是一種神奇的智慧生物,閱讀一段文字,不僅能從中獲取到資訊,更能從字裏行間感受到 情緒 ,有時,這份情緒甚至會蓋過資訊,影響他們做出判斷。因此,當你在參與評審時,請謹記這一點:保持謙遜、尊重他人,無論對方的經驗或背景如何。優秀的表達,能做到內容即使在批評,也能讓對方感受到自己仍是被尊重的。

讓我來舉一個例子。團隊內來了一位新人,用他不太熟悉的 Python 語言送出了一個 PR。作為 PR 的評審人,你在程式碼裏發現了一段冗長的迴圈程式碼,於是寫下評論:

程式碼比較啰嗦,建議改成列表推導式。

雖然你的觀點沒錯,但這種表達方式值得商榷。下面是這條評論的另一種寫法:

這裏的迴圈體較簡單,只有過濾和轉換邏輯,很適合改成列表推導式,程式碼更精簡。舉個例子:items = [to_item(obj) for obj in objs if obj.is_valid()]參考:https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions

比起第一條評論,後面這條顯然更不容易引起新人的抵觸情緒,更可能被采納。這就像那句老話所說: 「你表達的方式跟所要表達的內容同樣重要,如果不是更重要的話。」

除了保持謙遜和尊重,還有一些其他值得采用的評審溝通小技巧:

一例勝千言 :有時用文字洋洋灑灑寫一大堆,不如直接寫幾行程式碼,舉一個實際的程式碼樣例 見什麽人說什麽話 :對於加入團隊一個月和一年的開發者,你在評審時可以(也應該)區別對待;對缺乏經驗的新人,組織語言時要謹慎,盡量避免讓對方感覺不被尊重,產生太多的挫敗感;對熟稔的老人,語言風格就可以相對隨意,言簡意賅即可,不必過於啰嗦

我相信也許大部份人在心底都認同:程式碼評審是一個「對事不對人」的過程,不應該把對程式碼的批評當成對人的否定。但這和提倡「好好說話」並不沖突。一次讓雙方滿意的溝通,幾乎等同於一次更高效的溝通。所以,改善溝通方式就能提升工作效率,你又何樂而不為呢?

總結

程式碼評審作為保障軟體品質的重要手段,是大型軟體開發中不可或缺的重要一環。本文總結了我作為評審的參與者,在命名、指引性註釋和溝通方式三個方面的一些思考,要點如下:

程式設計師們應當不斷提高在程式碼評審時對於命名的敏感度 檢查命名的兩個技巧:同類名詞保持一致、用更精確的詞代替那些「萬金油」名字 對待名字不要一視同仁,多多關註那些最 重要 的名字 對任何一個計畫,領域(業務)相關的名字最重要,值得仔細斟酌、反復推敲 指引性註釋雖不提供太多程式碼之外的資訊,但也有著不可替代的作用 指引性註釋能大大降低人們為理解程式碼所付出的心智成本 留意指引性註釋的幾個陷阱:簡單復述程式碼、追求「註釋率」、不註重時效性 評審時,要勇於對復雜的程式碼邏輯提出補充指引性註釋的請求 軟體開發中包含許多與溝通有關的事項,程式碼評審正是其中之一 理想的評審是「有事說事,就事論事」的,但正因涉及人際溝通,導致現實往往偏離理想 文字不光能傳達資訊,更是情緒的載體,而情緒往往會影響溝通的效果 在評審中,永遠保持謙遜、尊重他人,無論對方的經驗或背景如何 對於有著不同經驗的待評審者,應當采取不同的溝通風格

程式碼評審是一項涉及多人協作的復雜事務,裏面藏著許許多多的學問。品質高的評審,對於提升品質和塑造團隊氛圍有著不可替代的作用。品質低下的評審,則可能淪落為形式主義,甚至讓團隊內部滋生矛盾和不滿。

而影響評審品質的因素,往往藏在那些不起眼的小細節、小事情中。以上這些關於「小事情」的經驗之談,希望能對你的工作有所啟發。

題圖來源: Photo by Helena Lopes on Unsplash

References

[1] 【程式碼整潔之道】: https://book.douban.com/subject/4199741/
[2] 一篇文章: http://antirez.com/news/124

以上是今天的分享,最後推薦一下我的【Python潮流周刊】專欄。

這是一個專為國內 Python 開發者量身打造的資訊平台,為你挑選最值得分享的文章、教程、開源計畫、軟體工具、播客和視訊、熱門話題等內容。

如果你覺得本文有幫助

請慷慨 分享 點贊 ,感謝啦