整個軟體開發的過程中,希望透過各種方式,能夠盡早找到其中的瑕疵,以便提早加以修正。一般來說,我們都會透過測試的過程,來試著達成這個目標。不過,有時也會用另一種手段來找出瑕疵,而且應用的時機更早於軟體開發過程的測試階段,這就是「程式碼審查(code review)」。

所謂的「程式碼審查」,就是透過查驗軟體的原始程式碼,來試圖找出軟體中可能存在的品質問題。當然,所用的審查方法,最好是一個系統性的方法,否則,只是亂槍打鳥在找問題,最後的結果也只能憑藉運氣好壞。

做什麼事都需要付出代價,程式碼審查也不例外。而程式碼審查進行的時機及進行的方式,都會影響做程式碼審查時所需付出的代價。時機愈頻繁,進行的方式所造成的額外負擔愈重,那麼付出的代價自然也就相對沉重。

有些團隊選擇當原始碼每次簽入至版本控制系統中時,都必須進行程式碼審查,而也有些團隊選擇在開發到達特定的里程碑時,才進行程式碼的審查,前後兩種頻率所需付出的代價,當然也不同。有些執行搭檔編程(pair programming)的團隊,甚至可以說他們在撰寫程式碼的同時,就已經在做程式碼的審查了。

審查進行有哪些標準程序?
另外,進行程式碼審查的形式,也可以從簡單、不那麼正式,到十分正式的形式。就拿較為正式的例子來做一個說明吧。

Michael Fagan在1970年代建立了一個檢驗的程序,之後有一些軟體審查的流程都是基於這個程序進行擴充或修改,像IEEE 1028所定義的正式軟體審查流程,即是以這套程序為基礎的。

首先,必須要有一個決定是否能夠進入審查的標準,當然,這個標準多半會以查核清單(checklist)的形式存在,對程式碼的審查而言,其中的項目就有可能像是程式碼能夠通過編譯之類的。進入審查之後,可以細分為幾個階段,包括了:

1.規畫(Planning):由一位主持人負責規劃審查的工作。

2.簡介會議(Overview Meeting):由被審查工作產物的作者來說明工作產物的整個背景,當然,對程式碼審查來說,工作產物就是程式碼。而作者要說明的,就是程式碼的規格、設計、以及基於何種解題的思維來寫下原始碼的。

3.準備(Preparation):每一位參與相關工作的審查者對工作產物進行檢查,以便找出其中可能存在的瑕疵。

4.審查會議(Inspection Meeting):在會議中由一位專職的閱讀者(Reader),來負責「讀出」被審查工作產物的每一個項目。當然,對程式碼的審查而言,閱讀者的工作比較像是導引者,帶領著大家一一走訪審查中程式碼的每一個片段。而在閱讀者「讀到」任何一位審查者覺得有瑕疵的部份時,審查者便指出該瑕疵。此時,記錄員必須將被指出的瑕疵予以記錄下來。

5.修改(Rework):工作產物的原作者,依據審查會議中的審查結果及結論,對工作產物進行修改,藉以修正原有的瑕疵。

6.追查(Follow-up):檢核原作者所做的修改、確定瑕疵皆已修正。

進入有標準,當然也有結束的標準,主持人有責任判斷是否審查已可結束。

從上述的過程,就能夠明白這樣審查的方式需要付出多少額外的代價及成本。牽扯進來的角色,包括了主持人、作者、審查者、閱讀者、記錄員五種角色。而需要進行的工作就有六種類型,甚至必須召開兩種會議。這樣的審查方式當然頗為正式、嚴謹,當然,代價也就難免沉重了。

審查的價值與範圍
也有不少人討論這種正式的審查方式是否划算。

如果我們能推算被審查的程式碼價值,再進一步依據程式碼的行數來評估參與角色所需付出的時間,那麼就大概可以知道需要付出多少工時在審查工作之上,也就可以明白為了審查這段程式碼,需要額外付出多少成本。但,值得嗎?

程式碼審查的目的,當然是在於提高品質,犧牲品質當然可以換回一些東西。當然,拿時間和人工去交換,也可以換回品質。這中間需要依據你所需要的品質、還有可能因此而付出來的代價去進行權衡。你可以降低審查的複雜度及嚴謹程度,來降低審查造成的額外負擔,也就能降低成本。每個專案的背景條件都不同,需要的審查方式自然也有可能因時、因地而制宜。

也有人建議,針對極為關鍵的程式碼進行較為嚴謹的審查,反之,則採寬鬆審查,我認為這也是。

每個系統中,總有一些位階核心、影響比較深遠的程式碼,它們是整個軟體系統的運作基礎,我們值得為它們的品質付出更多的成本,因此,我們可以選擇為它們進行較嚴謹的審查。反之,那些不屬於核心、關鍵的程式碼,基於成本的考量,可以在審查上採寬鬆的方式。畢竟,事有輕重緩急,成本有限的情況下,還是必須區分出優先順序。

審查者的挑選很重要。之所以需要其他的審查者來協助找出程式碼中暗藏的軟體,主要有可能因為作者本身的技術能力或經驗,並不像其他的同仁一樣好。透過能力更好、經驗更豐富的同事一起來參加審查,有助於找出因為能力、經驗限制而寫下的瑕疵程式碼。

再者,即使能力再怎麼好、經驗再怎麼豐富的程式設計者,都有可能犯下「當局者迷」的錯誤,這個時候「旁觀者清」的審查者,就比較容易看出原作者的思路陷阱,或沒有留意到的細節,也就能幫助找出瑕疵。

與透過編譯器檢查之間的差異
無論形式是否嚴謹,最終還是會有個具體審查的工作,這個工作的目的,就是要找出程式碼中的問題。那麼,程式碼中可能存在那些問題,可以透過程式碼審查予以找出呢?當然,我們的目標並非要找出不能編譯的部份──那是編譯器的工作。

我們在審查時,可能會審查一些基本的項目,像是:程式碼是否符合撰寫的規範(coding convention)、程式碼的函式是否符合規格或介面設計、是否有充足的註解協助讀者了解、有利日後的維護、程式碼是否有足夠的可讀性。這些審查的標準,雖然很基本,但也是程式碼本身品質的一環。

當然,我們也會更深入審查一些其他的項目,像是,演算法、資料結構的使用是否合適、程式的寫法是否會有效能問題、甚至有沒有需要被重構的壞味道(bad smells)。此外,有一些可能會造成安全性問題的寫法,像是會遭受緩衝區溢位攻擊、資料隱碼攻擊漏洞的寫法,都可以在審查中特別留意。而若能修正此類的瑕疵,審查工作的成本也就可以算是大大回收了。

有些程式碼的問題不容易察覺,像是可能存在競速條件(race condition)或是記憶體洩露(memory leak)問題的程式碼,也都是我們希望透過程式碼審查來加以找出的。

在審查時,其實可以依據一份標準的查核清單為基礎來進行審查。上頭記載各種常見、重大的問題,用以提供審查者一個具體的、系統化的審查方式。在一個團隊裡,可以透過持續進行的審查,找出容易發生的問題,便可反覆不斷的回饋到這份清單上。除了做為審查的依據之外,也可以提醒程式設計者究竟有什麼可能會犯下的錯誤。

現在,也有一些自動化的工具可以協助我們進行部份類型的審查(例如安全性的問題),也可以節省不少的人力。審查工作有利於程式碼品質的提昇,另一方面,也能透過審查過程來達到知識、技能的擴散,可以說是一舉多得。

專欄作者

熱門新聞

Advertisement