軟體品質之道
我曾與一些資曆非常高但毫無實際經驗的人共事過,也曾與一些只有很少或根本沒有資曆但才華橫溢的工程師一起工作過,我也曾經不得已跟一些並不想用心做事、也對學習新東西絲毫不感興趣的人共事過。如果說我們這個職業是一張紙,那麼這些人就好比紙上的汙點。軟體開發業的低劣性不能完全怪罪於那些無知的經理、狡猾的市場行銷人員以及總是急不可耐的使用者,實際上很大程度上要歸咎於這個行業的某些從業人員,他們應該去從事一些即使玩忽職守也不會造成像軟體業裡這樣大的危害的行當,而不應該混跡於這個聚集著人類想象力的最複雜的創造性的行業。
-- MatthewWilson
1 引子
最近,有同事請教了我一些關於軟體品質的相關問題,很抱歉我沒能給出一個較好的答案。主要原因是,這兩年來,一直忙碌在基本知識的學習總結上,雖然也接觸了很多這方面的內容,卻沒有一個很好的總結以及記錄,對此深表慚愧。
這一次的項目的啟動會議上,聽說了小韓的這麼一句話,大概是這樣子的,“我們作為一個互連網公司,不但要做一個優秀的產品,還要從產品上去教育群眾,引導社會”。加上這段時間看到前面Matthew Wilson的這段話,覺得自己作為這個行業的一員,肩負著重大的責任。
所以,藉此機會,對這兩年來關於親身經曆過的軟體品質管理進行總結。另外,聲明一下,首先,這隻是我對這方面的一些思考,可能跟其他人的想法不同,如果有其他想法的同學們,我們可以進行討論學習,以求共同進步;其次,個人觀點難免會和其他人發生衝突,如果對讀者們造成了冒犯,還請多多原諒和多多指教;最後,我是非常喜歡討論的一個人,因為每次討論都會有豐富的收穫,如果有同學們進行討論學習,我非常高興!
這部分跟整個主題(PerfectC++)可能不太符合,但是每一門語言都應該有對應的品質管理方式,畢竟每個軟體都不能保證沒有Bug,所以軟體品質也是重中之重,根本之根本。所以我依舊將其列入Perfect C++的一部分,但是內容應該也適用於大部分語言。
2 軟體品質
說道軟體品質,首先是如何進行衡量呢?我想大部分人想到的最簡單的一個衡量標準就是:這個軟體有多少個Bug?
這樣簡單的標準,其實是錯誤的。第一,軟體的Bug是不能進行統計的,因為在不同場合下,不同情境下,都可能存在Bug,但是這個數量卻是無法衡量的。第二,品質不能單靠“錯誤”來衡量,例如說軟體的執行效率等等,也應該作為標準之一。
說到這部分,我也只能引用《代碼大全》中的相關部分了(第20章軟體品質概述)。其中,軟體品質從外在特徵,即使用者角度來說,包括:正確性、可用性、效率、可靠性、完整性、適應性、精確性和健壯性;從內在特徵,即代碼角度來說,包括:可維護性、靈活性、可移植性、可重用性、可讀性、可測試性和可理解性等方面。
2.1 外在特徵
關於外在特徵,是我們經常關注的部分,因為站在使用者的角度來說,這就是使用者最關注的部分,使用者關心的,自然是我們最看重的。但是軟體品質並不能將這些細節都進行多度的處理,過於關注某個細節,就會對其他方面產生一定的影響,大概情況如下表:
|
正確性 |
可用性 |
效率 |
可靠性 |
完整性 |
適應性 |
精確性 |
健壯性 |
正確性 |
↑ |
|
↑ |
↑ |
|
|
↑ |
↓ |
可用性 |
|
↑ |
|
|
|
↑ |
↑ |
|
效率 |
↓ |
|
↑ |
↓ |
↓ |
↓ |
↓ |
|
可靠性 |
↑ |
|
|
↑ |
↑ |
|
↑ |
↓ |
完整性 |
|
|
↓ |
↑ |
↑ |
|
|
|
適應性 |
|
|
|
|
↓ |
↑ |
|
↑ |
精確性 |
↑ |
|
↓ |
↑ |
|
↓ |
↑ |
↓ |
健壯性 |
↓ |
↑ |
↓ |
↓ |
↓ |
↑ |
↓ |
↑ |
當然這隻是一些典型關係,在不同的項目中,可能影響恰好是相反的。
2.2 內在特徵
作為一名開發人員,應該對內在特徵進行思考。據我所知,其實大部分人是沒有關注這部分的,而這部分的影響又是非常大的。而這部分又是外在特徵的根本之所在,從我的角度來理解,內在特徵管理到位,會大幅度提高軟體品質。
2.3 普遍原理
說了這麼多,可能有很多人不是很在意軟體品質這個話題,但是想一下,一個人每天平均寫多少行代碼呢?其他時間都在幹什麼呢?我想大部分時間都是在調試,定位Bug,解決,再測試吧。
提高生產效率和改善品質的最佳途徑就是減少花在這種代碼上的返工時間,無論返工的代碼是由需求、設計改變還是調試引起的。
另外,要為軟體添加一個功能,時間消耗又是多少呢?我想一個與整體毫無關聯的功能添加要比在一個複雜邏輯的功能裡面追加要簡單的多吧。
所以,效果明顯的縮短開發週期的辦法就是改善產品的品質,由此減少花費在調試和軟體返工上的時間。
當然,上面這兩點都是在大量資料上得到的結論,具體請參考《代碼大全》第20.5章節:軟體品質的普遍原理。
3 改善軟體品質的技術
總說些不實用的話,很沒意思,所以趕緊跳過前面的涼菜,開始上硬菜!
3.1 主要方法
既然軟體品質分為這麼多方面,那到底該如何改善呢?改善的方法主要有:
1> 確定明確的軟體品質目標。首先要有明確的目標,如果目標不制定,那就如沙漠中行軍,最後也是全部滅亡的效果。
2> 確定保證這些目標要做的工作。其次,為了達到這些目標應該展開那些工作?只有目標,沒有工作計劃,相當於在原地休息,效果也是一樣的。
3> 確定明確的測試策略。測試不能改善軟體品質(這個跟現在工作的狀況恰好相反,真是讓我無力吐槽),根據測試結果來評估和改善軟體品質,就是大海撈針而已,撈到撈不到只能看運氣,並且還會導致測試人員苦不堪言。測試人員一定要提出明確的測試策略,不能將“測試”作為改善軟體品質的首要方法。
4> 確定明確的需求定義。需求定義一定要明確,很多情況下,需求人員定義的跟使用者想得到的恰好相反,這樣的開發都是無用功。
5> 確定明確的開發工程指南。開發工程指南包括上面各個方面的定義,應當控制軟體的技術特徵,貫徹所有開發活動之中,例如說代碼規範就是一個典型的代表。
6> 代碼複查。這個工作非常重要,不但可以發現大量的問題,還可以一定程度上提高人員的技術品質,但也有一些複查很不到位,過分關注細節,投入成本過高,收穫卻很少。
3.2 常用技術及Bug檢出率
下面是一些常用的技術和對應檢出Bug的檢測率,可以有一個直觀的瞭解。
檢錯措施 |
最低檢出率 |
典型檢出率 |
最好檢出率 |
非正式設計複查 |
25% |
35% |
40% |
正式設計複查 |
45% |
55% |
65% |
非正式代碼複查 |
20% |
25% |
35% |
正式代碼複查 |
45% |
60% |
75% |
個人代碼複查 |
20% |
40% |
60% |
單元測試 |
15% |
30% |
50% |
組件測試 |
20% |
30% |
35% |
整合測試 |
25% |
35% |
40% |
迴歸測試 |
15% |
25% |
30% |
系統測試 |
25% |
40% |
55% |
小規模Beta測試(10人以下) |
25% |
35% |
40% |
大規模Beta測試(1000人以上) |
60% |
75% |
85% |
上面的這些資料,應該是每一個測試人員都應該瞭解的。測試經理也並不是只是簡簡單單安排估算一下測試案例條數,分配用例編寫,安排人員進行測試,提交Bug而已。按照我個人的理解,確定軟體品質才是一個測試經理首要任務,測試只不過是其中最最基本的之一。如果單靠測試,那麼每個剛畢業的學生,經過幾個月,哪怕是幾個星期的培訓即可勝任,不提升自己的技能,後果大家都知道的。
下面分別針對上面的各個目標,為大家帶來一些工具和經驗的分析。當然這些也只在我的經驗範圍之內,歡迎大牛前來補充。
4 軟體品質的基本目標
提到軟體品質目標,首要要明確合格的代碼有哪些要求,主要指標如下:
1> 總行數:包括空行在內代碼的行數。
2> 函數循環複雜度:循環複雜度是指一個函數可執行路徑的數目。以下語句為循環複雜度的值貢獻為1,if/else/for/while語句,三元運算子語句,if/for/while判斷條件中的||或&&,switch語句,後接break/goto/return/throw/continue語句的case語句,catch/except語句等。
3> 函數深度:函數深度指示函數中分支嵌套的層數。
4> 語句數目:在C++語言中,語句是以分號結尾的。分支語句if,迴圈語句for/while,跳躍陳述式goto都被計算在內,預先處理語句#include/#define/#undef也被計算在內,對其他的預先處理語句則不做計算,在#else/#elif/#endif之間的語句將被忽略。
5> 分支語句比例:該值表示分支語句占語句數目的比例,這裡的“分支語句”指的是使程式不順序執行的語句,包括if/else/for/while/switch。
6> 注釋比例:該值指示注釋行佔總行數的比例。
7> 函數數目:函數的數量。
8> 平均每個函數包含的語句數目:總的函數語句數目除以函數的數目。
第一個要介紹的軟體為Source Monitor,這是一個代碼品質檢測的基本工具,即計算上述指標的工具。
對於開發人員來說,可以將其添加到Visual Studio、UltraEdit等等一系列的編輯工具中,具體添加方式可以參考Source Monitor的文檔。而測試人員需要瞭解如何使用命令列模式,建立一個工程,並篩選出不符合要求的目標。
下面是一個參考目標(來自於華為):
1> 循環複雜度7以下;
2> 最大行數1000以下;
3> 每個類最大方法數30以下;
4> 每個方法最大行數50以下;
5> 最大深度5以下。
5 代碼品質的基本目標
對於C++來說,檢查其代碼品質非常困難,尤其含有大量的指標,模板等方面,所以可以看到的程式碼分析工具並不多,一般大多採用PCLint進行靜態代碼檢查。
PCLint是一個非常嚴格的編譯器,可以發現很多編譯器並不提醒的錯誤,以次來提高代碼的品質,例如說識別記憶體越界問題,未初始化的變數,null 指標操作,冗餘的代碼,執行效率問題等等。
對於開發人員,每次提交代碼,都必須執行PCLint,保證修改代碼的安全性。對於測試人員,應該配置PCLint執行環境,定期對代碼進行掃描檢查,為開發人員提出警告。
PCLint的基本目標即無編譯Error和Warning。
6 測試目標6.1 單元測試
這裡提到的測試為單元測試,即開發人員主要關心的方面。但是需要測試人員進行管理控制。
理論上,凡是循環複雜度大於2的函數,都應該進行相關的單元測試的編寫。具體編寫的任務一般由該函數的作者進行完成,當然也有很多測試驅動開發的例子,但是這對一般的測試人員要求過高,需要對整體的架構以及代碼非常瞭解,能夠明確定義開發架構和函數才能勝任,所以就不過多贅述。
單元測試的工具,目前大多採用GTest、GMock以及MockCpp這幾個,mock工具只需選擇其一即可。
在這個環節,開發人員每次添加函數,如果循環複雜度較高,需要添加對應的測試函數。而測試人員應該每次構建時進行煙霧測試 (Smoke Test),保證所有測試函數通過,另外,應該結合循環複雜度的基準,如果循環複雜度較高卻沒有測試函數的,應該給與警告。
6.2 系統測試
這個方面需要測試人員關心,主要工具可以採用Visual Studio中整合的測試工程,然後錄製指令碼,通過執行指令碼,達到系統測試的目的。
一般測試之前需要測試案例的編寫任務,這個任務以前是開發人員進行編寫,這樣針對代碼的修改量,修改範圍,用例可以寫的非常詳細,針對性也非常強。參考基準目標為:測試案例數目/代碼修改行數 = 1/3。
測試結束,進行正式版本發布時的標準應為:失敗測試案例數目/有效測試案例總條數< 3%。當然,進行測試版本發布時,可以針對不同方面調節這個值的大小。
如果不滿足版本發布要求,應該針對Bug修改代碼,進行測試案例的追加,直到符合該範圍的要求,才能進行版本發布。
由於我不是測試人員,所以對系統測試這方面瞭解不是很多,包括可以用的工具進行自動化測試等,如果有大牛有經驗,歡迎指教。
7 需求明確目標
這一點主要依賴於產品評審會議,組織產品人員、開發人員、測試人員、使用者等進行產品需求的評審。這樣可以明確需求,同時可以精化需求,降低開發時間,快速進行迭代,推動產品的前進。
由於我對這方面的理解不是很深,歡迎有深刻理解的同學進行補充。
8 整合構建
前面針對軟體品質的各個方面的目標和達到目標的方法進行了描述,當然沒有代碼複查的相關部分,這個一般不同的公司組織方式不一,在這裡不進行贅述了。但是要把上面這幾部分綜合起來,進行自動化的控制,每天,或者每次代碼提交都進行檢查,那麼就可以大幅度減少人力,大家也可以抽出時間來進行交流學習,繼續提升自己,豈不是很好嗎?
既然說到這,達到目標自然也是可能的。首先,需要自動構建管理工具:Cruise Control,這個工具分為.Net版和Java版,當然最好還是選擇後者,因為可以進行搭配使用的工具很多。這個工具的主要功能是,提供一個伺服器端,使用者進行任務的配置,例如說編譯,各個目標的檢查,最後按照時間或者代碼的更新,執行各個任務的檢查,將失敗的任務告訴使用者。
其次,各個任務的執行,可以使用Ant,Ant是基於Java的一個build工具,使用Ant可以指定編譯期間不同的任務,然後Cruise Control中指定這些任務,就可以達到了自動化執行的目的。
最後,結果的統計與表示。Cruise Control只是一個管理工具,具體的資料需要測試人員進行收集,並統計對應的部分。例如本月一共提交了多少個用例,有多少執行失敗了,等等這樣可以表示描述軟體品質的資訊。為了達到這個目標,一般測試人員都需要掌握一種或多種指令碼語言。
9 進一步思考
軟體行業發展了這麼多年,大家都追隨在“敏捷”之後,我沒有從上一個時代走過,不能說這真的是“快了”還是“慢了”,品質到底是“高了”還是“低了”,大家都在拚命的趕時間,趕進度,趕版本。
最近總想寫點什麼,總結點什麼,話題很多,自己卻認識很淺薄,常識被那些大牛們踐踏的不敢說對還是錯,只感覺路越來越長,感謝這些偉人們,開拓了這條不歸路,我還有走完的那一天嗎?
時代在改變,技術在進步,即使沒有坐上最後一趟技術潮流的末班車,我們也應該步行走向未來!