大型C++專案必須注意的幾個小問題
有些問題對於小型的C++專案來說可能無關緊要,但對於大中型C++專案來講,這些問題卻成了大問題。什麼樣的專案算是小型專案呢,什麼樣的算是大中型專案呢,我認為10萬LOC以下為小型專案,10-50萬LOC為中型專案,50萬LOC以上為大型專案。當然,不能單純地以程式碼行數作為衡量標準,前幾天產品重構,我用四二三十行程式碼換掉了原來的三四千行程式碼,那這個專案的規模是用這二三十行來計算呢,還是用那三四千行算呢?軟體很難有一個準確的度量標準,暫以行數作為一種參考性標準吧。
當專案較大量一些在小型專案中不需要考慮的問題也變得非常重要了,對這些問題還是要認真考慮的。
1. 目錄結構
小型專案可能一個目錄就夠了,把所有標頭檔案和原始碼及相關的工程檔案放到一起,但專案較在時目錄中的檔案也就多了起來,檔案一多檢視就很不方便,如果對各種檔案執照功能或類別進行一些分組就會方便很多,比如查詢某個檔案也很迅速。這一點,我覺得boost和chrome做得是很好的,包括它們的命名規則都很統一,而ACE做得就不是太好,所有檔案放到一個目錄裡,好幾百個檔案,如果安裝了CVS或SVN的windows客戶端,在開啟資源管理器時還要掃描一下檔案是否與版本庫關聯,使得反應其慢,感覺不是太好。
2. 模組劃分
一個大型的專案不可能當成一個整體工程一下子編譯完的,如果有幾百個檔案,使用一個Makefile或者VS的工程,每編譯一次都會很耗時,如果需要經常除錯的話,編譯就會很消耗時間。如果把整個工程劃分成多個模組,不同模組以靜態庫或者動態庫的方式進行組織,各模組不僅可以分別編譯,單獨測試,而且對於多個人協作的並行開發是非常有利的。因為不論是VSS、CVS還要SVN等這些主流的版本管理軟體對於並行開發都不能做到盡善盡美,其實這個也不可能,因為軟體本身不可能識別程式,像人一樣去合併程式程式碼,如果幾個人會經常修改同一個模組的程式碼時衝突可能就比較多,解決衝突的次數多了不僅影響工作效率,而且還影響心情。
3. 標頭檔案層次
為什麼把標頭檔案的層次結構單獨分出來而不是直接把模組劃分放到同一個問題裡呢?就是要突出其重要性。如果在劃分模組時沒有把模組的組織方式設計合理,很有可能導致標頭檔案迴圈引用的情況,這樣程式就無法編譯。
有一些特殊情況就更需要注意,比如windows中的winsock2.h和windows.h存在衝突,如果先引用windows.h再引用winsock2.h就會出現重定義的情況。如果想使用Win32 Socket API 2,則必須先引用winsock2.h然後引用windows.h,可是如果在多個檔案裡引用windows,如果順序控制不好就有可能導致混亂,從而程式無法編譯。個人認為最好的方法就是把該引用放入一個頭檔案,比如叫sysinc.h,然後所有編譯單元的標頭檔案都在第一行引用sysinc.h,然後所有的.cpp檔案在第一行引用對應的標頭檔案,這樣即可保證winsock2.h和windows.h的引用順序。
4. 名稱空間
C語言是不支援名稱空間的,以前人們為了避免識別符號衝突都是加字首或者字尾,但這樣會使得識別符號很長,看起來不舒服,而且寫起來很麻煩(雖然現在IDE對智慧感知的支援已經使這個變得很方便),還老感覺怪怪的,不夠自然。所以,後來的語言都對名稱空間(或類似功能)進行了支援,如C++、C#、Java、Python等,所以在大型的C++專案為什麼不用這個新的特性呢?
使用不同名稱空間中的識別符號識不要圖省事使用using namespace ns; 這樣的語句把整個ns命令空間內的識別符號都引進來,這樣便失去了名稱空間的作用。因為這樣做可行是需要一個前提的,就是知道這個不會衝突,可是誰能預知未來不會衝突呢?前些天就遇到在WINDOWS、Linux、AIX三個平臺編譯都沒有問題,可是到了Solaris就出了問題,原因是在系統的標頭檔案if.h中有一個結構定義struct map,這個跟C++標準庫中的模組map衝突,導致無法編譯。
在這方面,boost的作者們不愧是C++的專家,這方面做得很好,而ACE就不是太好,所有識別符號通過字首ACE_來標識,保必呢,而且是大寫,看起來很不舒服,如果要用呢只能忍了,或者自己單獨寫一個頭檔案,使用typedef或者用巨集定義把識別符號都給改了,然後把新定義的識別符號封裝到一個ace命令空間中,這也是個不錯的主意。
5. 程式碼註釋
時間長了誰也難保證記住每一段程式碼的用途,特別是一些特別技巧,一定要隨手寫下來,為以後自己維護或與同事合作都是很必要的,不要怕別人看懂自己的程式碼對自己有威脅,開放一些,只有互相學習才能促進發展,你把程式碼給了他人,他人也會給你建議幫你進步的,中國前一百年的歷史就是固步自封的沉痛教訓,國家如此,個人亦如此。
6.版本管理工具
如果幾個人同時維護幾十萬行程式碼都沒有使用版本管理工具的話,那你每天肯定只能有一種感覺,那就是在浪費生命。浪費自己的生命也就算了,如果作為一個領導者不對此做點改變的話,那就是浪費別人的生命,也就是謀財害命。
7. 程式碼規範
每個人可能都有自己的編碼風格,但是同一個專案,應該只有一種風格,否則可能會影響工作,雖然程式本身的效能和功能都沒有會有問題,但對於產品的實際開發過程會有影響,因為它會影響到一些有想法的工程師的心情,當然就會影響工作效率,因為他們老感覺那些程式碼不夠規範,總想去修改這些細節,當改過後可能會被另外的工程師改成另一種風格。所以必須有統一的風格,以統一團隊每一位成員的行為。
有一個細節,我還是有體會的。專案中一些舊的程式碼寫得很亂,有的一個函式都能寫幾百行,而且程式碼塊的花括號{ 和}都是另起一行if-else都是在單獨的行,這樣著滿屏都是花括號和if - else,看起來程式碼很不緊湊,比如:
if( cond 1 )
{
line …
line …
}
else
{
line …
}
改成
if( cond 1 ){
line …
line …
}else{
line …
}
看起來就緊湊多了。
還有一些程式碼層次很深,一塊程式碼可能都有四層if條件巢狀,看起來是很費勁的,如果在每一次條件判斷都進行退出的話,可讀性將會有很大的改善,如
if( cond 1 ){
if( cond 2 ){
if( cond 3){
work line
}
}
}
如果改成下面程式碼,看起來會更舒服些
if( ! cond 1)
return –1;
if ( ! cond 2 )
return –2;
if( !cond 3 )
return –3;
work line
當然寫成
if( cond 1 && cond 2 && cond 3 ){
work line
}
更好,但有些時候並沒有這麼方便。
8. 自動化構建
如果一個大型專案包含幾十個小模組,每次都要手工編譯依賴項的話,工作效率是低下的,如果能夠自動構建必要的模組將可以大大提高工作效率,即使每次能少輸入幾個字元也是必要的。
比如我們的一個專案需要在windows、Linux、AIX、HPUX、Solaris五個平臺上執行,如果每次編譯前都執行configure,然後再執行make,也不是很方便,如果公共的部分寫到一個Makefile.comm中,然後為每一個平臺的平臺依賴項分別寫一個Makfile.win,Makefile.linux, Makefile.aix,Makefile.hpux, Makefile.solaris,這樣每次只需要輸入make –f Makefile.os來編譯,但這樣要輸入的要挺多的,可以再建一個Makefile檔案,然後自動檢測系統平臺,然後呼叫對應的Makefile,以後只需要輸入make就行了,再想簡單,可以寫個小指令碼,檔名為m,裡面呼叫make指令,這樣每次只需要輸入./m即可,如果還嫌輸入的字元太多的話,可以把.加到你的PATH變數中,以後直接輸入m就可以了。如果再想簡單呢?那隻能使用特異功能,玩兒心靈感應了,我實在是想不到其它的辦法了。
只是個例子而已,可能實際開發過程中需要更多其它的工作,但當你發現你做某件事時不再是第一次,你就可以考慮下次遇到後如何簡化了,這也是“程式修煉之道”中提到的DRY(Don’t Repeat Yourself)法則。
9. 自動化測試
對於一個大型專案,每次構建後都進行手動測試的工作量是很大的。如果公司沒有研發團隊配備測試人員,而且測試部不能給及時測試的話,研發力會將會有大量的人力浪費在測試上,所以構建自己的自動化測試系統是一項至關重要的內容。
暫時談到這兒吧,想到再記下來,有機會與網友探討,各位晚安。