本文轉載于 https://www.cnblogs.com/yulia/p/7363438.html
原文出自騰訊移動品質中心TMQ http://mp.weixin.qq.com/s/0ZyIvSosAlebNM4bATftww
導讀
最近兩年,品質中心極力推動測試工作左移,以期能提前發現産品的問題,降低成本。筆者自認代碼基礎能力還不錯,就想通過代碼Review來提前發現一些Bug。
多數項目中,代碼評審工作是由開發同僚互相執行的。但往往開發同僚為了趕進度,并沒有時間進行代碼評審,導緻很多明顯的Bug被遺留到了測試階段。那代碼評審是否可以由測試人員來做呢?顯然是可以的。誠然多數測試人員的代碼能力沒有開發人員的水準,代碼Review的深度不如開發同僚,但通過實踐證明,測試人員也能勝任大部分代碼評審的工作。
做CodeReview的方法
筆者在剛開始做代碼Review時也是毫無頭緒,不知道哪些代碼可能有問題。那時我才意識到了解Bug出現的根因對代碼Review有至關重要的作用。
通過對Bug及開發對應修改的代碼進行分析,并與開發同僚交流,我了解到一些Bug出現的原因,以及出錯代碼的一些特征。當這些代碼特征被總結出來後,我将這種特征用于Review其他的代碼,此時能慢慢地能發現一些Bug了,但效率比較低。
後來用Android Studio自帶的Lint工具掃描代碼可以掃描出大量疑似缺陷的點,再通過人工分析可以發現不少空指針和邏輯上的問題,Review代碼的效率得到了極大的提升。
但還有一些更深層次的需求,比如像一些多條件組合的代碼就不能通過Lint掃出來了。是以我把這些特殊的代碼特征進行彙總,請一個同僚幫忙寫了一個定制化的代碼掃描工具,利用這個工具掃描出代碼位置,然後針對性的Review。

總結我的實踐過程,建議剛開始做代碼Review的朋友,先使用一些業界常用的工具快速入手。當積累一些經驗後,嘗試自己分析問題并總結經驗,好的經驗積累起來形成自己的知識庫和工具庫,提升Review效率。
Review知識點彙總
以下是筆者在平時工作中總結出一些經常可以發現問題的點,希望對同仁們有所幫助。
1、空指針
如果項目有異常上報統計,就會發現最常見的異常是空指針異常(NullPointerException),代碼中如果使用了未初始化的對象都會導緻這個異常。一般開發都會在程式入口處進行參數的判空,不過這樣還不夠。嚴格意義上,任何一個對象在使用前都應該進行判空處理。
如下代碼片斷所示,一些開發同僚習慣當傳入參數為空時,直接傳回一個空的對象。單從本方法的角度來看是不會有問題的,但是在調用本方法的地方,如果忘記做判空處理就會出現空指針的錯誤。
以上示例中較好的代碼實踐是傳回一個沒有元素的清單,或者是當參數為空時直接顯式的抛出一個異常,讓調用者必須處理該異常。
針對空指針的情況,一般Review以下幾點:
(1)方法參數如果不能為空時,是否做了判空處理,或者在方法調用者傳入參數時是否確定了不為空;
(2)方法是否有傳回null的情況,如果有是否可以改為傳回一個空白對象(如沒有元素的清單等);
(3)當被調用的方法(如系統方法)傳回為null時,調用者是否有進行判空處理;
(4)使用的對象是否在使用時已經被初始化。較常見出現問題的情況是類的成員,如果在構造函數中沒有進行初始化,而在其他地方進行初始化時,初始化時機是未知的,那麼此時對象使用前一定要進行判空。
2、邏輯判斷
(1)邊界判斷
數組越界(OutOfBoundaryException)在異常統計上報中也是比較常見的問題,這是最常見的一種邊界條件不正确引起的問題。
數組或者清單邊界一般Review的點有以下幾個:
1) 數組或清單的循環中,合法下标範圍是0<=K<list.size();
2)通過下标從數組或清單取資料時,下标不合法的判斷方法是if (k < 0 || k >= list.size());
3)當在下标存在加減時,需要判斷當加上或減去某值後,是否可能存在越界的情況;
4)如果是分隔字元串産生的數組,取數組的值前一定要判斷下标是在數組長度範圍内的;
5)取數組或清單的項時,需要首先判斷數組或清單的長度不為0。
(2)邏輯判斷
任何一個if語句都有兩個分支。當僅有一個if時,開發一般不會漏掉if-else兩個分支。
但如下面的示例代碼,本身可能不存在問題。但可以看出組合起來的條件分支會有很多,當if-elseif-else組合嵌套時,開發同僚會重點關注滿足需要條件的情況,卻往往容易忽略else應該做的處理。像以下的示例代碼,也要思考是否能将判斷條件組合來用,減少嵌套。
另外多條件組合的判斷邏輯,特别是判斷條件超過兩個時,或者是“&&”與“||”組合使用時也非常容易出錯。
如下的示例代碼,首先這段代碼不容易了解,看到這段代碼時需要想想“&&”與“||”哪個的優先級高,如果用括号包起來就會更容易了解;其次經過詳細分析後發現最終結果與isCacheCurrentChapter的值無關。
又如下面的示例代碼,doSomething的方法接受的參數不為空,然而當a的值為空時會中斷後續判斷邏輯,b即使為空也會傳入到doSomething方法中,導緻doSomething不能正常運作。
是以,對于以上類似的判斷邏輯代碼,可以做的評審有三點:
1)是否能優化判斷邏輯,使代碼更加簡潔易懂;
2)是否所有的分支都得到了合理的處理,如代碼中沒有寫出來的else分支,或者Switch的default分支;
3)是否存在條件判斷的中斷情況,對後續一些判斷或者邏輯造成影響。
3、函數中途傳回
函數中途傳回指在運作過程中, 達到了某種條件, 使程式中途return的情況。
如下面的代碼所示,當info為空時直接傳回了,乍一看似乎沒有任何問題;但如果認真地思考後,會發現container對象還在等待一個回調,Review時需要去檢查沒有執行這個回調方法是否會存在問題。
是以針對類似的在中途傳回的情況,Review時需要看看是否存在return導緻某些邏輯不能正确執行到的情況。
4、記憶體洩漏
當程式偶爾出現莫名其妙的卡頓或異常,又或者Crash上報出現OOM異常時,那作為測試人員就該意識到程式有記憶體洩漏了。
記憶體洩漏除了通過專門的測試方法來測試外,也可以通過代碼Review來發現。
對QQ浏覽器的記憶體洩漏測試發現的Bug原因分析,發現導緻記憶體洩漏最頻繁的原因不是圖檔資源或者IO流(Stream)未釋放,而是注冊了事件未取消注冊引起的記憶體洩漏。
如下面的示例代碼所示,FooActivity将自己注冊到了FooDataManager,便于在資料發生變化時自己能收到通知。
如下面的代碼所示,FooDataManager一般都會用一個清單來存儲注冊的監聽者,如果FooDataManager需要運作很長時間甚至整個生命周期,或者listener本身是一個靜态對象的話,那麼listener會長期存在于記憶體中,這意味着listener中存放的對象也會被長期持有,最終導緻記憶體洩漏。
前面示例中的FooActivity并未将自己反注冊,listener一直持有該對象造成記憶體洩漏。
以上問題看起來似乎很簡單,但是在浏覽器項目中,即使進階的開發工程師也會犯類似的錯誤。當然記憶體洩漏的原因還有很多,這裡就不全部列舉了,大家可以網上搜尋進行了解。
針對記憶體洩漏的情況,我一般會Review以下幾種常見情況:
(1)對象如果注冊了事件回調,是否在合理的地方進行了反注冊;
(2)線程對象使用完畢是否正常的結束;
(3)各種資料庫、網絡連接配接和檔案IO被打開後,是否正确關閉;
(4)圖檔資源正确釋放;
(5)緩存對象要有一定的大小控制,且有明确的釋放政策。
5、異常處理
關于異常處理的評審,筆者一般會關注當異常被捕獲後,是否正确的處理,以及當有異常處理後,後續的流程是否正常執行。
如下面的代碼所示,當catch到異常時,此時looper是為空的,到後續的Handler初始化傳入空的looper程式會出錯。
效果
代碼評審在QQ浏覽器漫畫子產品最近了三個版本進行了實踐,共發現Bug25個,如下面的截圖所示。由于代碼Reviwe在開發階段就進行,Bug發現的時間提前了至少一周。
總結
以上是我的一點經驗總結,還需要持續積累。
萬事開頭難,個人以為做代碼Review在剛開始的時候會稍微難一些,但隻要做到以下幾點一定能做好代碼Review。
第一,學會使用一些業界比較常用的代碼掃描工具,可以快速入手;
第二,堅持學習提升自己的代碼能力,并掌握快速閱讀和了解代碼的方法;
第三,加深對自己産品的業務和代碼結構的了解,更容易發現深層問題。
最後,學會通過Bug根因分析,總結經驗并應用于平時的工作中。
以上内容分享給大家,與大家共勉,希望我們一起進步!