目錄
0. 引言 1. 程式碼檢視的指導思想 2. 程式碼檢視的內容 3. 迴歸測試
0. 引言
程式碼檢視(Code Review)是指軟體開發人員在完成程式碼設計、編寫、除錯後展開的個人或群體性的程式碼閱讀過程,程式碼檢視的目的是發現程式碼中的設計問題、格式問題、邏輯問題、語法問題等,從而保證程式碼的高質量交付。從軟體工程的角度講,在程式碼檢視階段發現程式碼問題的成本是低廉的,所以嚴格認真的執行程式碼檢視過程,是提升產品質量,降低產品維護成本的有效手段
Relevant Link:
http://www.distorage.com/code-review%E5%B0%8F%E8%AE%BA/ http://www.ibm.com/developerworks/cn/rational/11-proven-practices-for-peer-review/
1. 程式碼檢視的指導思想
codereview主要包含以下幾個觀點
1. 一次評審少於 200–400 行的程式碼。 2. 目標為每小時低於 300–500 LOC 的檢查速率。 3. 花足夠的時間進行正確緩慢的評審,但是不要超過 60–90 分鐘。 4. 確定程式碼開發者在評審開始之前就已經註釋了原始碼。 5. 為程式碼評審和獲取制度建立可定量化的目標,這樣您才能改進流程。 6. 使用檢查列表,因為它可以極大地改進程式碼開發者和評審者的作品。 7. 確認缺陷確實得到修復了。 8. 培養良好的程式碼評審文化氛圍,在這樣的氛圍中搜尋缺陷被看做是積極的活動。 9. 警惕"老大"效應。 10. 最少評審一部分程式碼,就是您不能評審全部的程式碼,以從 Ego Effect 中受益。 11. 採用輕量級,能用工具支援的程式碼評審。
需要特別注意的是,即搜尋缺陷是好的事情,而不是糟糕的,缺陷密度與開發員的能力並不是掛鉤的。記住對一個團隊來說,缺陷,尤其是團隊成員所引入缺陷的數量不應該被迴避,也不應該用作能力的評價引數。
程式碼檢視作為一個勘誤的過程,核心目的是提升程式碼質量,同時提升開發人員的能力,而不是批鬥和責備開發人員,要積極引導參與人員的程式碼檢視態度,把程式碼檢視作為提升開發人員能力的一項活動來進行,這樣才能達到程式碼檢視的核心目的。同時,程式碼檢視需要包含對設計的檢視,更多的時候需要程式碼開發者講解其設計思路
0x1: 一次評審量要低於 200–400 行程式碼
開發員的評審量要低於一次 200-400 行程式碼(LOC Line Of Code)。超過這個量,搜尋缺陷的能力就會降低。以這個速度,您可以找到 70-90% 的缺陷。換句話說,如果存在 10 個缺陷,那麼您可以找到其中的 7 到 9 個
圖中描述了缺陷密度與評審程式碼行數量之間的關係,支援該規則。缺陷密度 就是每 1000 行程式碼之中所發現的錯誤(bug)數。評審程式碼行的數量超過 200 時,缺陷密度就會急劇地下降。
在這種情況下,缺陷密度就是"評審有效性"的評價手段。如果兩個評審員評審相同的程式碼,其中一個發現了更多的錯誤(bug),那麼我們就會認為該評審員更有效率
0x2: 每小時低於 300–500 LOC 檢查率的目標
快速評審並不總是好的。研究結果顯示檢查率低於"300-500 LOC/小時"時,可以得到優化的結果。根據所作的決定,評審者的檢查率有很大的變化,就算是相似的程式碼開發者、評審者,檔案和評審規模,也存在巨大的差異。
為了找到優化的檢查率,我們將 缺陷密度 與評審者檢查程式碼的 速度 進行了比較。得到的結果再一次落在了我們的預料之中:如果在評審您不花足夠的時間,那麼您就不會發現太多的缺陷。如果評審者面臨大量程式碼的壓力,那麼他就不會每一行程式碼投入相同的注意力。他不能研究同一位置處更改的所有版本
調查結果顯示
1. 每小時小於 400 LOC 的評審速度下,效率是相對較高的 2. 每小時超過 400 LOC 的評審速度會降低效率
0x3: 花足夠的時間進行適當緩慢的評審,但是不要超過 60-90 分鐘
永遠不要對一個原型程式碼評審超過 60-90 分鐘
我們應該討論,為了得到更好的結果,不應該過快地評價。但是您也不應該在一個位置花太多的時間。大約 60 分鐘後,評審者就會感到疲勞,於是就不會找到額外的缺陷了。這個結論得到了許多其他研究的支援。實際上,根據我們的常識,當人們從事注意力高度集中的活動時,效能狀態在 60-90 分鐘之後就會降低了。考慮到人體方面的限制,評審者在效能降低之前,不能評審超過 300–600 行的程式碼。
但反過來說,評審程式碼所花的時間不得低於五分鐘,就算程式碼只有一行也是如此。通常來說,單行的程式碼也會影響到整個的系統,所以花上五分鐘時間去檢查更改可能造成的結果是值得的。
0x4: 確定在評審開始之前程式碼開發者已經註釋原始碼了
0x5: 為程式碼評審建立可定量化的目標,並獲取制度,這樣您就可以改進流程了
有了專案,您就該決定程式碼評審過程的目標,以及怎樣評價效率問題了。當您在定義特定的目標時,您就能夠決定同行評審是否真的達到了您所需要的結果。最好從外部性的制度開始,例如
1. "將支援訪問降低 20%" 2. "使開發引入的缺陷百分比減半"
這些資訊使您能夠更好地看清,從外部視角來看,程式碼能夠做些什麼,您還需要一個可定量化的評價手段,而不是"修復更多錯誤(bug)"的模糊目標
但是,在外部制度顯示結果之前需要花上一段時間。例如
1. 支援性訪問將不會得到影響,直到新的版本得到釋出並交到客戶手中為止
0x6: 使用檢查表,因為它能極大地影響程式碼開發者和評審者的結果
使用檢測表對於評審員非常重要,如果程式碼開發者忘記了某項任務,評審員也同樣可能忘記
使用檢查表的優點有
1. 使用檢查表來避免可能會忘記的事情,它對程式碼開發者和評審者都有用。忽略是最難發現的缺陷 2. 評審不在那裡的東西是很困難的一件事。檢查表是解決這個問題的最好方式,因為它會提醒評審者和程式碼開發者花點時間去考慮一下可能被遺忘的事情 3. 檢查表還會提醒程式碼開發者和評審者確定 1) 所有的錯誤(bug)都得到了處理 2) 軟體功能已經通過了無效值測驗 3) 而且已經建立了單元測試
0x7: 確認缺陷得到了修復
使用團隊協作工具,例如JIRA是進行bug追蹤的一個很好的方案
0x8: 培養良好的程式碼評審文化氛圍,在這樣的氛圍中可以積極地評審缺陷
與其他我們能看到的大多數技術相比,程式碼評審對於真實團隊構建能夠發揮更大的作用,但是隻是在管理人員能夠以一種積極的,向上的,有技巧的方式進行交流時,這種優勢才能發揮出來。將缺陷看做是不好的事物很容易(畢竟,它們是程式碼之中的錯誤(bug)),但是形成不好的缺陷檢查態度,則會毀掉整個團隊的努力,更不要說它會破壞錯誤(bug)檢查過程了
軟體程式碼評審的要點在於
1. 儘可能多的消除缺陷,不管是誰"導致"了錯誤(bug) 2. 管理人員必須建立缺陷是積極的這樣的觀點。畢竟,每一個缺陷的存在,都是改進程式碼的潛在機會,而錯誤(bug)評審過程的目的,就在於使程式碼儘可能地完美。每一個被發現並解決的缺陷,都是客戶以後不會看到的缺陷,也是 QA
人員不必花費時間去解決的問題 3. 團隊需要維持這樣一種態度,就是發現缺陷,就意味著程式碼開發者和評審者 作為一個團隊 去改進產品的質量成功了。而不是"程式碼開發者產生了一個缺陷,而評審者負責去發現它"。它更像是配對程式設計的一種有效形式。 4. 評審員要向所有的開發者展示收集壞習慣,學習新技巧,並展開功能的機會。開發員可以從他們的錯誤(bug)中學習,但是隻是在他們警惕錯誤(bug)時才會這樣。如果開發員害怕發現錯誤(bug),那麼積極的結果就會消失。 5. 如果您是一名初級開發員,或者是一個團隊的新成員,那麼其他人發現缺陷時,就意味著您強有力的隊友在幫助您成長為一個合格的開發員。這就比您單槍匹馬地程式設計,沒有具體的反饋時,要更快地進步
0x9: 警惕老大哥效應(Big Brother Effect)
0x10: 評審一部分的程式碼,就算您不能全部完成,以從自我效能感(Ego Effect)中獲益
0x11: 採用輕量級,工具支援的程式碼評審
2. 程式碼檢視的內容
既然有了程式碼檢視的指導原則,那麼在程式碼檢視中,我們究竟檢視些什麼內容呢
1. 與詳細設計方案的一致性 只要將檢視的程式碼對照詳細設計進行比較就很容易檢查出程式碼是否和詳細設計一致,採用逐行逐字閱讀進行比較的方法進行。 2. 標頭檔案檢查 標頭檔案檢查主要關注以下方面: 1) 是否包含有多餘的其他標頭檔案 2) 標頭檔案是否內聚,即是否多個模組共用一個標頭檔案 3) 標頭檔案內的內容是否清晰,是否分類排放好並給出了足夠的註釋 4) 是否使用了象 #ifdef __LIST_H__ 之類的巨集定義保證標頭檔案不被重複引用 3. 巨集定義檢查 1) 巨集定義中有引數和表示式時,引數和表示式是否都用括號括起來了。例如: #define ADD(a, b) (a + b) //正確的應該是 ((a) + (b)) 這個定義中就沒有將引數a和b括起來,如果使用時a和b是表示式的話,就會因為運算子順序問題而出問題。 2) 續行符\是否使用正確 4. 常量 常量方面主要檢查的主要問題如下: 1) 常量是否使用了巨集來進行定義 2) 程式中是否存在魔鬼數字 3) 16進位制資料是否在前面加上了0x 4) 常量是否來自規格 5) 不來自規格的常量的值是否合理 5. 全域性變數與共享變數需要檢查的主要問題如下: 1) 全域性變數是否必須的,是否可以改成區域性變數 2) 是否有全域性範圍內變數和區域性範圍內變數重名情況 3) 是否有多個任務訪問共享變數,是否進行了有效的保護 4) 當全域性變數只限於本檔案內使用時,是否定義成靜態的 5) 多個任務讀寫共享變數時,是否可以將讀寫操作封裝成獨立函式,而不是在每個模組裡都進行加鎖解鎖操作 6. 靜態變數和函式 靜態變數和函式檢視時主要問題如下: 1) 靜態變數的使用是否正確 2) 每次使用靜態變數時是否需要重新初始化 3) 對不需要重新初始化的靜態變數在多次使用後是否有溢位的問題。 4) 檔案內部使用的函式是否要定義成靜態的 7. 資料結構 資料結構方面考慮的主要問題如下: 1) 資料結構裡的成員型別定義是否正確 2) 結構體裡面變數順序安排是否合理,資料是否對齊 3) 是否存在冗餘未用的成員變數。 4) 類裡面是否有私有變數和私有函式放到了公有的定義裡去了 8. 初始化 初始化考慮的主要問題如下: 1) 變數使用前是否需要初始化 2) 類的建構函式中是否對需要初始化的成員都進行了初始化 3) 陣列的初始化是否正確 4) 記憶體或陣列在每次使用前是否需要初始化清零 5) 多個變數初始化賦值時是否存在順序問題 6) 靜態變數和全域性變數的初始化是否存在初始化順序問題 9. 字串 1) 字串是否以'\0'結尾 2) 字串是否會超長 3) 字串使用的空間大小是否存在差1問題 4) 使用字串指標時,指向的位置是否存在差1問題 5) 字串指標是否可以為空,為空時會有什麼現象 6) 字串內容為空(即第一個字元為'\0')時會發生什麼現象 7) 字串中如果有轉義字元"\"字元時,是否正確地寫成了"\\" 8) 在對字串進行拷貝或連線操作時,是否對空間大小進行校驗,防止出現緩衝區溢位 10. 輸入檢查 輸入校驗需要檢視的主要問題如下: 1) 函式引數是否需要進行檢查 2) 從檔案讀取的資料是否進行校驗 3) 使用全域性資料時是否需要進行校驗 4) 通訊收到的資料是否需要進行校驗 5) 從訊息中接受到的資料是否需要進行校驗 11. 邊界條件 凡是牽涉邊界條件的地方都需要進行邊界檢查,以下的一些問題供參考: 1) 迴圈變數上的邊界是否正確 2) 變數的取值是否有邊界條件限制,邊界是否給出並書寫正確 3) 空間邊界,如記憶體大小,陣列大小是否正確,是否存在差1和越界情況 4) 資料結構邊界,如連結串列的頭一條記錄和最後一條記錄等邊界情況 5) 伺服器連線數量最大是多少 12. 記憶體分配和釋放 記憶體分配方面需要檢查的有以下幾點: 1) 分配的大小是否正確,是否分配了過大的記憶體或者分配的記憶體大小不足,分配的記憶體大小是否存在差1錯誤 2) 記憶體分配是否經過判斷或者進行異常處理 3) 重新分配一塊記憶體時,是否將原有記憶體釋放 4) 分配的記憶體是否需要初始化清零 5) 是否有在大迴圈中不斷分配記憶體導致可能出現系統記憶體不足情況 釋放方面需要檢查的有以下幾點: 1) 所有的分支路徑上是否將分配的記憶體進行了釋放 2) 是否將已經釋放的記憶體重複釋放 3) 釋放的是否是空指標 4) 釋放多塊記憶體時是否存在釋放的先後順序問題 使用realloc()時要考慮以下幾點: 1) 新增空間是否需要初始化清零 2) 是否還有指標指向老的記憶體塊,並在realloc()後使用指向老的記憶體塊的指標。 13. 型別轉換 型別轉換的檢查有以下問題供參考: 1) 型別轉換是否採用安全的轉換機制 2) 當採用強制轉換時是否會出問題 3) signed 和 unsigned 轉換是否存在問題 4) 是否將小空間的型別轉換成了大空間的型別 5) 型別轉換是否會造成截斷、溢位或越界 14. 陣列使用 陣列的使用也是很容易出錯的一種,不幸的是現在還沒有足夠好的方法能保證陣列越界一類的問題得到完美的解決,所以通過對陣列的檢視來保證質量就很重要了,下面給出檢視陣列的一些建議: 1) 型別是否正確 2) 多維陣列是否資料存放順序正確 3) 陣列使用時是否會越界,空間大小是否存在差1錯誤 4) 作用域是否正確 5) 陣列大小是否太大導致浪費 15. 指標使用 指標在C/C++中是使用最廣泛的一種語法,指標使得語言的功能強大起來,但也給程式質量帶來了很大麻煩,使用指標時是極易出錯的,可以說C/C++程式碼中的缺陷大部分都與指標有關,下面給出檢視指標的一些問題參考: 1) 指標是否初始化 2) 指標型別定義是否正確 3) 使用前是否申請了記憶體 4) 引用是否正確,是否引用了釋放掉的空間 5) 指向的空間是否正確 6) 是否存在使用野指標現象 7) 釋放後再使用時是否需要重新初始化 8) 是否使用了空指標,函式指標是否為空就被呼叫 9) 指標是否需要校驗 10) 指標進行型別轉換時是否會引起問題 11) 指標地址運算是否有誤,在地址相加時是否考慮了相加的數字要乘以指標型別所佔空間的大小。比如int *p; p+1相比p的大小不是大於1,而是大於一個整數所佔空間的位元組數 16. 函式 函式方面的一些檢視建議如下: 1) 函式呼叫的引數傳遞是否正確 2) 是否有形參和實參使用錯誤的問題 3) 函式的返回值和輸出是否需要校驗 4) 呼叫的函式是否對全域性資料產生影響 5) 函式功能是否單一,是否在函式裡處理了多個不同的功能 6) 函式引數是否需要定義為const 7) 函式是否過長(一般以不超過200行為宜) 17. 系統和標準庫呼叫 呼叫系統函式和庫函式時,以下一些檢視建議供參考: 1) 系統呼叫是否正確,呼叫引數設定是否正確 2) 是否按照標準文件中的要求和注意事項進行了呼叫 3) 對於存在BUG的系統函式是否採取了規避措施進行呼叫 4) 對呼叫系統函式是否需要在呼叫前進行了輸入校驗 5) 呼叫後是否需要對輸出進行校驗 18. 判斷迴圈條件 在程式中的判斷和迴圈條件中,也存在著一些有時通過測試難以發現的問題,主要的檢視建議如下: 1) 邏輯運算子是否正確,如| 和||,&和&&運算子是否搞混淆掉或鍵盤失誤寫錯, 2) 邏輯等號==是否誤寫成等號= 3) 運算子順序是否正確,運算子| 、&,||、&&,=、== 的運算順序需要特別注意 4) 迴圈判斷中的表示式是否正確地使用了括號將運算順序區分開,並增加可讀性 5) 表示式運算是否存在邏輯上的錯誤 6) 對浮點數是否誤用了精確相等進行比較 7) 迴圈變數是否進行了初始化 8) 迴圈的中止條件是否在某些情況下無法到達而造成死迴圈 9) 迴圈的邊界上是否會造成問題 10) 判斷條件是否會恆真或恆假 19. 計算 計算錯誤也是程式中經常遇到的一個問題,大部分計算錯誤可以經過測試發現,但並不是所有的計算錯誤都可以很容易通過測試來發現,以下的一些問題供檢視時參考: 1) 計算表示式或公式是否書寫正確,需要逐字元地進行確認沒有輸入錯誤 2) 表示式中運算子順序是否書寫正確,同優先順序運算子運算時是否存在自左至右結合或自右至左結合運算結果不同的問題 3) 是否需要使用括號來保證運算順序的正確性和增加程式的可讀性 4) 是否存在計算溢位情況,如兩個整數相乘結果超出整數最大範圍等情況 5) 截斷誤差和舍入誤差是否會引起問題,誤差是否會累積下去導致誤差越來越大 6) 是否存在除零問題(零做分母的問題),或者兩個整數相除結果得到零然後再和其他整數相乘 7) 是否存在某個變數會累積增加導致長時間執行後的溢位 20. 資源釋放 資源釋放方面的一些檢視建議如下: 1) 所有的資源是否都進行了釋放 2) 要檢查是否存在某條路徑遺漏了釋放 3) 控制程式碼釋放 3.1) 開啟檔案是否關閉了 3.2) 訊號量是否釋放 3.3) 控制程式碼是否關閉 3.4) 鎖資源是否釋放 3.5) 是否存在死鎖問題 4) 全域性的資源是否存在隨時間累積增加不減少的問題 5) 其他各種資源如網路socket等是否在各條對應路徑上進行了關閉 6) 類的解構函式中是否對類中需要釋放的成員進行了釋放 21、效率 從空間和時間效率方面一些檢視建議如下: 1) 多個if判斷是否可以改為switch或if…else if結構 2) 多個相同處理的case語句是否歸到一起 3) 在多重迴圈中,最忙的迴圈是否在最內層 4) 迴圈體內的判斷語句是否可以移到迴圈體外
3. 迴歸測試
迴歸測試是指修改了舊程式碼後,重新進行測試以確認修改沒有引入新的錯誤或導致其他程式碼產生錯誤
自動迴歸測試將大幅降低系統測試、維護升級等階段的成本
迴歸測試作為軟體生命週期的一個組成部分,在整個軟體測試過程中佔有很大的工作量比重,軟體開發的各個階段都會進行多次迴歸測試。在漸進和快速迭代開發中,新版本的連續釋出使迴歸測試進行的更加頻繁,而在極端程式設計方法中,更是要求每天都進行若干次迴歸測試。因此,通過選擇正確的迴歸測試策略來改進迴歸測試的效率和有效性是非常有意義的
在實際的產品研發和迴歸測試中,可以選擇一個時間段之後集中進行一次迴歸測試,針對和上次相比變化的功能點進行迴歸重點回測測試
Relevant Link:
http://baike.baidu.com/view/106720.htm?fr=aladdin
Copyright (c) 2014 LittleHann All rights reserved