C++ 編程過程中,有哪些常犯的壞習慣,哪怕對於多年經驗的程序員也會出現?

(more)effective c++ 里說的都是好習慣,很少有人全部做到吧,但是有哪些不好的習慣經常出現呢,高人們來說說,讓我等菜鳥少犯錯誤吧


說說我見到的一些不良現象吧。有些程序員幹了十多年還這樣。

1.命名空間。

不用namespace。導致全局空間被污染。組織混亂帶來維護障礙或使用不便。或完全依靠前綴把名字起的老長,在樹後面都能看見。

2.代碼一鍋粥。

不使用介面隔離實現/頭文件設計不當。

文件間依賴過度緊密難於分離。或過於鬆散造成使用困難。以及包含次序耦合帶來的編譯問題。

3.預編譯頭失調。

要麼完全不用,造成編譯慢;

要麼用的太多,在預編譯頭裡「隱藏」了一些本該被include的定義,使功能在庫外部無法使用。

4.大清江山萬萬年。

抵觸任何變化。拒絕使用c++11及更新的語言特性。即使編譯器支持也不用auto,不用lambda表達式,不用override……

把業務邏輯寫得冗長複雜。

5.狂熱者

對c/c++盲目推崇,對其它語言盲目貶低。

如用性能差貶低C#和Java;

如認為所有有GC的語言都是解釋執行的/都是弱類型的/都是自動管理資源的。

直到被piapia打臉。

6.憑直覺優化

不會或不愛使用性能分析手段。很多人你跟他談結構不好,他開口閉口說「為了性能」跟你打嘴炮。然後你一問具體數據就變成了:我覺得會更快……

7.愛SM黨

濫用內嵌彙編迷信其的效率和逼格。代價包括開發效率差,無法跨平台,不便閱讀,無法享受編譯器優化……

常常只是把簡單問題複雜化,複雜問題天書化。

8.暗中破壞。

不寫或濫用防禦代碼。

或不檢查指針有效性。由函數依賴傳入參數決定是否崩潰。

或用assert代替,崩潰後連日誌輸出都沒有。發布後出了問題就抓瞎。

或有檢查,但失敗後保持沉默,把問題隱患擴散到其他地方。

9.異常。

對異常不了解也不想了解。

或完全不用導致某些邏輯複雜。

或混淆c++異常與操作系統異常。

或隨意catch並忽視異常。

或沒有對應防禦機製造成內存泄露/漏過初始化等問題。

10.華麗的參數表。

不對參數封裝或抽象為對象。使得介面難用/易出錯/無謂的參數copy。

11.不加甄別的繼承c遺產。

如隨意的類型轉換/類型擦除/函數參數默認值/函數變參/濫用union代替轉換函數等等。代碼建立在過多的隱喻上。

12.濫用const。

過分強調使用const,近乎原教旨主義。綁架其他的介面也無謂提升複雜度。一點需求變更就引發散彈槍式的重構。

13.閉門造車

因懶得了解stl而造一些無聊的輪子。排序、搜索、數組、字元串……都單寫一套。問題多,效率差,還不通用。

14.永遠的繼承。

從不組合。一說擴展功能就想到繼承,甚至多重繼承。把類型寫得龐大臃腫。最後發現很難不動介面做任何改動。然後完蛋。

15.四通八達。

從不封裝數據成員。並且喜歡直接訪問靜態成員或全局變數。飛線如亂麻。模塊間高度耦合,數據變更失控。一有新需求就傻眼。

16.寫虛函數總是不會錯

濫用虛函數,鼓吹應一切皆虛。直到某天在構造函數里調了一下……

17.多愁善感。

從不畫圖,理由是覺得沒必要/能想明白/不會畫/習慣先寫再畫。結果就是業務邏輯混亂,代碼層次模糊,對象生存期說不清楚。隨意持有其他對象指針。總抱怨意料之外的事發生。

18.酷愛吃糖

濫用operator/模板/宏來打造語法糖。把簡單代碼寫到編輯器推導不出才滿意。

19.CPU:「怪我嘍」

最常見的是把經過運算的float直接和定值比較。出現問題怪CPU不靠譜,把他的數「算壞了」。

20.warning是啥?

認為能通過編譯就大功告成了。然後埋一個如分支無返回值之類的雷到運行時。

21.只要能用就好。

分不清平台API/專屬庫與C/C++標準庫。

比如用MFC在win下寫伺服器。然後發現在Linux上無法編譯部署。

22.懶癌晚期

懶得寫或不會寫測試。從不用代碼測試代碼,拿測試人員或用戶當小白鼠。

或把一段簡陋的臨時代碼插到程序某處運行。第一次能跑就把測試刪除了。

23.性能一筋

過度具象的盲目追求「性能」。

設計時言必提性能。

茴字100種寫法全都為性能。

高估函數調用開銷。

高估new/delete的開銷。

然後使用dowhile處理分枝。

……

把代碼搞得像(友善度)。

24.我最專一。

只用一種技術/語言/平台。一切沒聽過的東西都不存在。

25.甩手掌柜。

基礎問題不想了解。

如不理解字元編碼與傳輸格式的區別;

如不知道各種調用約定的區別;

如不知道結構成員對齊的含義(只有1位元組對齊時代碼才能工作)

……

其實就是懶得花幾分鐘看看書的事。

26.金口玉言。

代碼從不重構。也不許別人動。當你指出某某行缺陷,對方卻說NN年都用過來了,肯定靠譜。

27.卡牌:誤導

注釋與代碼對不上,命名與作用都不上,變數名與類型對不上。

每次看到如「軍銜」(rank)與「角色等級」(playerLevel)兩個變數相比較時就想抽人。

28.神奇的前綴。

當你看到m_lprpglpmcCur時,會想到這事啥?會理解成當前的RPGLocalPlayerMotionController*嗎?這縮寫完全是把人搞昏。另外即使沒有IDE提示,我也認為這東西一點用都沒有。縮小變數作用範圍才是更好的選擇。

29.到處都是坑

不了解語言/庫的特性卻亂用。

如算符優先順序問題;

如表達式取值次序問題;

如不同版本vector實現差異問題;

如臨時對象做參數傳遞問題;

……

天天嚷著語言坑,其實(友善度)。

30.大魔術師。

如把棧上的char[]cast為某類型後,手動調構造/析構。玩憑空造物。

如在對象里克隆自己、幹掉自己、再把新對象塞回自己的管理器里。玩金蟬脫殼。

……

總之就是有話不好好說,多種方式花樣作死。


先open, 然後n多處理, 末了close, 自以為天衣無縫。

誰知道「 n多處理」 之中,拋出了一個異常。。。

然後他「吃一塹長一智」,到處加上

try ... catch(...),自以為萬無一失!

最後他的代碼只能扔掉,由「會的人」重寫。

============== 2014-10-24 補充 =======================

那麼問題來了,到底該怎麼釋放局部資源呢?

回答是 Resource Acquisition Is Initialization (RAII)。

簡單地說,『清理/釋放』工作, 要放到局部變數的dtor中去做。

例子:

class AutoCloseFd // "fd"型資源的自動釋放類

{

public:

 AutoCloseFd(int fd)

 {

  _the_fd = fd;

 }

 ~AutoCloseFd()

 {

  close(_the_fd);

 }

int _the_fd;

};

我們怎麼用它呢?

void do_many_thing()

{

int fd = open( 打開一個文件,或者通訊管道。。。 );

if (fd &<0 )

 {

// 沒錯,我們喜歡拋異常,但是一般不抓異常。

// 只有在"頂層入口點"或者,抓了之後會有B計劃而不是"報錯,返回",這兩種情況,才會去"try catch"

throw OurException( "Failed to open xxx, errnor=%d" ,errno );

 }

// 現在fd已經成功獲得,我們要確保它一定會關 。這就是RAII:

 AutoCloseFd __we_wont_forget_to_close_this_fd (fd);

//然後你就不用管這個fd了

 do_some_thing_may_throw_ex(fd); // 完全不用擔心

 do_some_thing_other_may_throw_ex(fd); // 我有RAII,還怕它throw?

}


多年C++經驗,簡單羅列一下,權當拋磚引玉:

  1. 非底層的業務項目中,不使用stl、boost等成熟組件,自己造輪子
  2. 自己管理內存,不使用智能指針
  3. 在頭文件中using namespace
  4. 使用指針前,不判斷空指針
  5. 數字、指針變數聲明時,不賦初值
  6. 不使用虛析構函數
  7. 沒有充分利用RAII(智能指針、析構)機制的前提下,使用異常


我曾經看代碼,有人為了實現Clone操作把operator=聲明為virtual的,這個我覺著好危險。


不了解線程並發,一個真實的案例:

由hashmap管理的對象的指針,取出來加引用計數,放回去減引用計數,減到0就釋放對象,用鎖保護引用計數。

這個保護引用計數的鎖,被放在了每個對象里……


在頭文件里using namespace


難道不是學會了鎚子到處找釘子嗎?


最壞的習慣就是,在可以選擇的情況下,選擇為了兼容C語言而被迫存在的那些feature。


分配了內存忘記了釋放

打開了文件忘記了關

吃了飯忘記了洗碗

拉了屎忘記了擦屁股


//聲明一個靜態整型變數
static int asdinfadga;
//聲明一個雙精度浮點型變數
double xxx;

我最受不了上面這種代碼,但是看別人代碼的時候經常有。(重點是注釋)


總覺得C++可以干一切事情,守舊。

不願意學習其他語言其他工具,這真是戰略上的壞習慣。

編程世界中還有另一番天地。

至於使用上的壞習慣(戰術層面),一是太多了。

二跟新手們講了,其實也沒用,只有自己濫用吃了苦頭,才會吃塹長智。

所以說最好的捷徑是沒有捷徑,慢慢來吧,不用操之過急。


1:寫C-Style C++代碼

2:隨處可見的傳值(你考慮過複製開銷么)

3:分配內存之後不管了等著操作系統擦腚(內存泄露)

4:一切皆為struct(class的保護被你吃了?所有成員都公開?)

5:一堆一堆又一堆的裸指針(當然有時候不得不這麼干,不過少干點更安全)

6:胡亂重載operator

7:寧願用#define也不用static const

8:說了一萬遍的全局using namespace

9:寧願用foo_hello()也不用namespace


當標準庫函數isalpha的參數傳入一個比較大的整數後,程序coredump了。。。


1、重複造輪子。

2、 }


用裸指針


輕易猜測並相信某個API是某種特定的實現;

不仔細看API說明,特別是注意事項,就根據以往的經驗直接上手;


拒絕學習新語言

或者帶著舊的視角看新語言


使用兼容C的API和數據結構

昨天幫室友de了個bug,他在搞在搞深度學習,一堆我看不懂的演算法,最後跪在了哪你們猜猜看

char vec[12];
sprintf(vec, "%012d", number);

根本問題在哪?

不是vec[12]太少——就算你開了13或者1024,也不能避免有一天sprintf(str, "%1024d",然後掛掉

問題在C


大部分編譯器允許做的事情,談不上習慣好壞,不同的公司和項目,風格不一樣,水平不同,要求也不一樣。

像我自己就會覺得沒有先寫好測試代碼就實現功能很危險,但不見得這麼做就有問題。


習慣問題不好說,但是大膽預測造成問題的原因中,與指針相關的不低於60%


推薦閱讀:

大學每天用6個小時編程,以後會怎樣?
stl的sort和手寫快排的運行效率哪個比較高?
C++輸出hello world,請從電子電路、內存CPU、程序層面解釋一下?
如何在一個月內提高C++水平?
如何說服同學在寫C++程序的時候用cstdio而不是stdio.h?

TAG:程序員 | 編程 | 信息技術IT | C | 程序員能力 |