?? 學習c++實踐者的方法.txt
字號:
By 劉未鵬(pongba)
C++的羅浮宮(http://blog.csdn.net/pongba)
前言
我的blog以前很長一段時間關注的都是C++中的技術&細節,乃至于讀者和應者都寥寥。然而5月份的時候寫的一篇“你應當如何學習C++”,閱讀量卻達到了3萬多,在blog上所有文章中卻是最高的(且遠遠超過了第二位);評論數目也有一百多。為什么獨獨這篇能夠激起這么多的回應,想必是國內的C++社群被C++壓抑太久,或者,嚴格來說,是被C++的教育方式壓抑太久。實際上,不管是在各大國內論壇上,還是在comp.lang.c++.moderated這樣的國際C++論壇上,乃至于在douban上的小組內,有心者都會發現,對C++語言的細節的關注一直都沒有停止過,同樣,對C++語言的細節的抱怨也從來都沒有停止過。一個例子就是comp.lang.c++.moderated上的一個技術牛人James Kanze說的,他說接觸C++十年了,到現在還需要不時去翻C++標準。這就難怪Eric Raymond老大在《The Art of Unix Programming》中說“C++是反緊湊”的了。C++中的細節太多,就算都看過了,也不可能都記住。更關鍵的是,就算都記住了,也不能讓你成為一個真正的好程序員。
絕大多數人都把細節太多(或者用貶義詞來說就是“陰暗角落太多”)歸結為C++的本質問題,認為一切邪惡由此而生。也正因此,大約9月份的時候,Linus在郵件列表上說“C++是一門有思想包袱的語言;僅僅是為了讓程序員遠離C++,我也要用C”。這句短短的話在國內引起了很大的反應,最初是劉江轉了Linus的話,然后云風和孟巖都發表了自己的看法;我也寫了一篇“Why C++”(后來發給Bjarne,Bjarne對這篇文章做了一個友情評注)。
然而,這一通渾水攪過之后,我相信引起的變化未必很大。大多數原先的反對者能從中找出反對的理由,于是更加反對;大多數原先的贊同者也能從中找到贊同的理由,于是更加贊同;而剩下來的原先沒有明確意見的,看雙方各有各的道理,可能還是沒有頭緒。
擺脫自我服務偏見——理性思考的前提
《決策與判斷》上提到過一個有趣的真實故事:1980年的某一天,美國空戰司令部的計算機突然發出警報——蘇聯的一枚核彈正在向美國本土飛來。司令部立即調兵遣將,迅速為一場核戰做好了準備,然而3分鐘之后,工程人員發現是計算機的一個小零部件故障造成的。然而,這場虛驚之后,大眾的反應才是真正有意思的:原先支持核武裝的,認為現在感覺更加安全了(因為“事實證明這類的故障是完全可克服的”);而原先反對核武裝的則認為更不安全了(因為“這類錯誤信號可能導致蘇聯過度反應,引發真正的核戰”)。類似的情況也發生在三里島核泄露事件之后,同樣的,反對者認為(“這表明管理部門沒有辦法安全管理核能”),支持者認為(“這正表明這樣的危險沒有想像得那么嚴重,是可克服的”)。社會心理學把諸如此類的現象總結為“自我服務偏見”。不幸的是,“真理越辯越明”其實只適用于理性思考者。
為什么啰嗦這么一大通呢?就是因為,一直以來泛濫于程序員社群的“語言之爭”,背后真正的原因其實并不在于語言實質上的優劣,而在于觀察者的眼睛。在觀察者的眼睛里面,語言并非一門工具,而是自己花了N多時間(其中尤數C++為最)來“修煉”的技能,對于這樣的技能,被否定無疑等同于自己被否定。所以,從心理學上講,語言并不是工具(盡管一直有這么一種呼吁),而是信仰。這樣的信仰在越是花得時間久的語言上越是激烈。有趣的是,幾乎所有的“熱鬧”的社群都有這樣的現象,Java、Python、Ruby…莫不如是;因為就算語言本身不復雜,程序員仍然還是要投入大量的精力去學習各種各樣的框架類庫(想想Java的那些框架?)。因此這些語言社區的信仰未必不比C++社群的強烈。
然而,一旦弄清我們為什么會把語言當成信仰,就非常有助于擺脫在看待語言時的“自我服務偏見”,從客觀的角度去看待問題。——“當你看到的是支持某個意見的證據時,試著去想一想有哪些證據是不支持它的”。
那么為什么要擺脫自我服務偏見?說小了,是為了成為一個更優秀的程序員(誰也不希望因為偏見而去使用一門低效的語言乃至不妥當的語言)。說大了是節省生命(因為偏見可能導致越陷越深,浪費時間)。
所以,如果你能夠理性的思考我們將要討論的問題,避免自我服務偏見(就當你從來沒有花時間在C++上一樣)。那么我們便可以開始討論真正的問題了。
前言2
現在,幾乎每個學習C++的都知道C++的核心問題是其復雜性;甚至本身不在C++社群的,也知道這是事實。群眾的眼睛是雪亮的,何況這還是個太顯而易見的事實。
但看了無數篇闡述C++復雜性的文章,和爭論C++復雜性的吐沫星子(包括我前段時間寫的兩篇關于C++的總結)。我始終都有一個感覺——沒分析透,就跟盲人摸象一樣。正如“Why C++”的一位讀者批評的,我在文章里面沒有寫明到底哪些是C++的“非本質復雜性”。當然,我自己憑感覺就能知道,而接觸C++一段時間的人大致也能知道,但新手乃至非新手則對我所謂的“非本質復雜性”根本沒有一個具體的認識,這就使得那篇“Why C++”脫離了原本的意圖——面向所有C++使用者和學習者。
同樣的原因,在寫了“你應當如何學習C++”一文之后,當孟巖先生邀請我給《程序員》寫一個系列的文章,介紹一下我在接觸C++的過程中的態度和認識轉變時,我雖然非常高興的答應了,但直到現在3個月過去了還是顆粒無收。為什么?因為我覺得真正本質的問題沒有被清晰的觸摸到;所以直到現在我都沒有動筆,免得廢話說了一大堆,除了能被當成小說讀讀之外,對真正考慮是否要學習乃至使用C++的人未必有什么實際用處。
然而,這么個念頭一直都放在潛意識里面。前一陣子和Bjarne通信,談到了關于C++復雜性的一些想法,在郵件里面總結了一下C++的復雜性來源,感覺思路清晰了許多。而這篇文章要達到的目的,正是傳達對C++的復雜性的一個具體而明確的認識,有了這個認識作為支持,我們便可以推導出學習C++的最佳(實踐者)的方法。
為什么要學習(并使用)C++
顯然,如果找不出要學習C++的理由,那么談什么“正確的學習方法”等于是廢話。
首先重復一句Bjarne的話:“我們的系統已經是極度復雜的了,為了避開C++的復雜性而干脆不用C++(Linus的做法),無異于因噎廢食。”在所有可用C和C++的領域,C++都是比C更好的語言。當我說“更好的”時候,我說的是C++擁有比C更安全的類型檢查、更好的抽象機制、更優秀的庫。當然,凡事都有例外,如果你做的項目1)不大。2)編碼中用不到什么抽象機制,甚至ADT(抽象數據類型,例如std::complex這種不含多態和繼承的)也用不到,RAII也用不到,異常也用不到。3)你連基礎庫(如,簡化資源管理的智能指針、智能容器)都用不著。那么也許你用C的確沒問題;所以如果你的情況如此,不用和我爭論,因為我無法反駁你。我們這里說的領域大致是Bjarne在“C++應用列表”里面列出來的那些地方。
底線是:如果把C++中的諸多不必要的復雜性去掉,留下那些本質的,重要的語言特性,簡化語言模型,消除歷史包袱。即便是C++的反對者也許也很難找到理由說“我還是不用C++”。在我看來,一個真正從實踐意義上理性反對使用C++的人只有一個理由:C++的復雜性帶來的混亂抵消乃至超過了C++的抽象機制和庫(在他的特定項目中)帶來的好處。
值得注意的是,這里需要避免一個陷阱,就是一旦人們認定了“C++不好”,那么這個理由就會“長出自己的腳來”,即,就算我們拿掉C++的復雜性,他們可能也會堅持還是不用C++,并為之找一堆理由。我假定你不是這樣的人。不過,也許最可能的是他會說:“問題是我們今天用的C++并非如此(簡潔),你的假設不成立。”是的,我的假設不成立。但雖然我們無法消除復雜性,我們實際上是可以容易地避開復雜性,避短揚長的。這也是本文的要點,容我后面再詳述。
當然,到現在你可能還是會說。我還是不用C++,因為我可以用D;或者如果你本來做的項目就不需要C++,你則可能會說,我用Python。首先,如果你的項目能用Java/Python乃至Ruby做,那么用C++是自討苦吃。因為能用那些語言代表你的項目在效率上本身要求就不高,那么用一門效率上討不到太大好處,復雜性上卻綽綽有余的語言,有什么價值呢?其次,如果你的項目效率是很重要的,你可能會說可以用D。然而現實是D在工業界尤其是國內被運用得非常少,幾乎沒有。而C++卻有大量的既有代碼,已經使用C++去做他們的產品的公司,在很長一段時間之內幾乎是不可能用別的語言重寫代碼的,正如Joel所說,決定重寫一個非平凡的代碼基==自殺。所以,我們至少要注意以下兩個明顯的事實:
事實1:C++在工業界仍有穩定的核心市場。
這個事實大概不需要多加闡述,很多大公司的核心技術還是要靠C++來支撐的(見Bjarne主頁上的C++應用列表)。所謂事實,就是未必是大家最愿意承認的情況,但又不得不承認。C++積累了龐大的代碼基,這個代碼基不是一朝一夕能夠推翻的。D從語言角度來說的確優于C++,但最關鍵的就是還沒有深入工業界(也許根本原因是沒有錢支持,但這不是我們討論的重點)。而C呢,根據Bjarne本人的說法,他的觀察是主流工業界的趨勢一直是“從C到C++”的,而不是反過來,至少在歐美是如此。在國內我們則可以通過CSDN上的招聘情況得到一個大致類似的信息。
事實2:C++程序員往往能享受到有競爭力的薪酬。
是的,這不是一篇不食人間煙火的技術文章。這個事實基于的邏輯很簡單:物以稀為貴。Andrei Alexandrescu這次來中國SD2.0大會的時候,在接受采訪時也說過:“最賺錢的軟件(如MS Office)是C++寫的”。孟巖也在blog上提到這么個事實,我想他作為CSDN的技術總編,業界觀察肯定比我清晰深刻。所以我這里就不多廢話了。
當然,以上邏輯并不就意味著在慫恿你去學C++,一切還要看你的興趣。所以如果你志不在C++身處的那些應用領域,那這篇文章并非為你而寫。
“C++的復雜性是根本原因”——一個有漏洞的推理
一旦我們認識了C++在一些領域是有需求的(值得學習和掌握的)這個問題之后,就可以接下來討論“怎樣正確學習和掌握C++”這個核心問題了。
其實,對于這個問題,Bjarne已經宣傳了十年。早在99年的時候Bjarne就寫了“Learning C++ as A New Language”,并在好幾篇技術訪談(這里,這里,這里,還有這里)里面提到如何正確對待和使用C++中支持的多種抽象機制的問題。Andrew Koenig也寫了一本現代C++教程《Accelerated C++》(這本書后面還會提到)。然而這么多年來,C++社群的狀況改善了嗎?就我所知,就算有改善,也是很小的。學習者還是盲目鉆語言細節,只見樹木不見森林;網上還是彌漫著各種各樣的“技術”文章和不靠譜的“學習C++的XX個建議”;一些業界的有身份的專家還是在一本接一本的出語言孔乙己的書(寫一些普通程序員八輩子用不著的技巧和碰不著的角落);而業界真正使用C++的公司在面試的時候還總是問一些邊邊角角的細節問題,而不是考察編程的基本素養(不,掌握所有的語言細節也不能讓你成為一個合格的程序員)。這個面試理念是錯誤的,估計其背后的推理應該是“如果這個家伙不知道這個細節,那么估計他對語言也熟悉不到哪兒去;而如果他知道,那么雖然他可能并不是好的程序員,但我們還是能夠就后一個問題進一步測試的”,這個理念的問題在于,對語言熟悉到一定程度(什么程度后面會具體建議)就已經可以很好的編程了(剩下的只需查查文檔);而很多公司在測試“對語言熟悉程度”的時候走得明顯太遠了(比如,問臨時對象生命期和析構順序當然是無可厚非的,但問如何避免一個類被拷貝或者如何避免其構建在堆上?);當然,有些語言知識是必須要提前掌握的,具體有哪些后面會提到,面試的時候并非不能問語言細節,關鍵是“問哪些”。
所以說:
事實3:C++的整個生態圈這么些年來在學習C++的哲學上,實在沒有多少改善。
為什么?是因為Bjarne介紹的學習方法在技術上沒有說到點子上?是Andrew Koenig的書寫得不夠好?說了誰也不會相信。因為實際上,這里的原因根本不是技術上的,而是非技術的。
眾所周知的一個事實是,從最表層講,C++的最嚴重問題是在語言學習階段占用了學習者的太多時間。翻一翻你的C++書架或者電子書目錄,絕大多數的C++“經典”都是在講語言。在我們通常的意義上,要“入門”C++,在語言上需要耗的時間一般要兩三年。而要“精通”C++,則搞不好需要耗上十年八年的。(這跟Peter Norvig說的“十年學習編程”其實不是一回事,人家那是說一般意義上的編程技能,不是叫你當語言律師。)
那為什么我說“C++的復雜性是根本原因”是個有漏洞的推理呢?因為,要讓人們在使用一門語言去做事情之前耗上大量時間去學習語言中各種復雜性,除了語言本身的復雜性的事實之外,還有一個重要的事實,那就是學習者的態度和(更重要的)方法。而目前大多數C++學習者的態度和方法是什么呢?——在真正用C++之前看上一摞語言書(日常編程八輩子都未必用得到)。而為什么會存在這樣的學習態度呢?這就是真正需要解釋的問題。實際上,有兩方面的原因:
事實4:市面上的絕大多數C++書籍(包括很多被人們廣泛稱為“必讀經典”的)實際上都是反面教材。
也就是說,隨便你拿起哪本C++書籍(包括很多被人們廣泛稱為“必讀經典”的),那么有很大的可能這本書中的內容不是你應該學的,而是你不應該學的。我之所以這么說有兩個原因,因為一,我曾經是受害者。二,也是更實質性的原因,這些所謂的必讀經典,充斥的是介紹C++中的陷阱和對于C++的缺陷的各種workarounds(好聽一點叫Idioms(慣用法)或techniques(技術));又因為C++中的這類陷阱和缺陷實在數不勝數,所以就拉出了一個“長尾”;這類書籍在所有語言中都存在(“C缺陷和陷阱”、“Effective Java”、“Effective C#”等等),然而在C++里面這個尾巴特別長,導致這類書數不勝數。三,這些書中列出來的缺陷和陷阱根本不區分常見程度,對于一個用本程序員來說,應該希望看到“從最常見的問題到最不常見的問題”這樣的順序來羅列內容,然而這些書里面要么全部混在一起,要么按照“資源管理、類設計、泛型”這樣的技術分類來介紹內容,這根本毫無幫助(如果我看到一個章節的內容,我當然知道它講的是類設計還是資源管理,還用廢話么?),使得一個學習者無法辨別并將最重要的時間花在最常見的問題之上。
最最關鍵的是:這些書當中介紹的內容與成為一個好程序員根本毫無關系,它們頂多只能告訴你——嗨,小心跌入這個陷阱。或者告訴你——嗨,你知道當你(八輩子都不一定遇到)遇到這個需求的時候,可以通過這個技巧來得以解決嗎?結果讀了一本又一本之后,你腦袋里除了塞滿了“禁止”、“警戒”、“燈泡”符號之外,真正的編程素質卻是一無長進。又或者有這樣一類書,熱衷于解釋語言實現背后的機制,然而語言特性本質上是干嘛用的?是用來在實際編碼中進行抽象的(說得好聽一點就是“設計”),不是用來告訴你這個特性是怎么支持的。比如我就見過以下的情景:面試官問:“你知道虛函數嗎?”得到的回答是一堆關于虛函數表機制的解釋。面試官又問:“那虛函數的好處是什么呢?”到底為什么要虛函數呢?得到的回答是:“恩…啊…就是…多態吧”(這時已經覺得回答不夠深刻了)。再問:“那多態是干嘛的呢?”啞口無言。
事實5:就算記住一門語言的所有細節也不能讓你成為一個合格的程序員。
事實6:了解語言實現固然有其實踐意義(在極端場合的hack手法,以及出現底層bug的時候迅速定位問題),然而如果為了了解語言機制而去了解語言機制便脫離了學習語言的本意了。
在C++里面這樣的情況很多見:知道了語言實現的底層機制,卻不知道語言特性本身的意義在什么地方。本末倒置。為什么?書害的。二,這類書當中介紹的所有情景加起來其實只屬于那20%(二八法則),甚至20%都不到的場景(究竟是哪些書,后面會介紹,我不便直接列出書名,打擊面太大,但我會把我認為essential的書列出來)。這就是為什么我說“八輩子都用不著”的原因。
事實7:80%的C++書籍(包括一些“經典”)只涉及到20%(或者更少)的場景。
你可能會說,那難道這些書就根本不值得看了嗎?
我的回答是,對。根本不值得看。——但是值得放在旁邊作為必要的時候的參考(記住從索引或目錄翻起,只看嚴格必要的部分),如果你是個嚴肅的程序員的話。因為不管承認與否,墨菲法則的強大力量是不可忽視的——如果有一個可能遇到的陷阱,那么總會遇到的。而同樣,C++的那些奇技淫巧也并非空穴來風,總有時候會需要用到的。但是你不需要預先把C++的所有細節和技巧存在腦子里才能夠去編程,即:
建議1:有辨別力地閱讀(包括那些被廣泛稱為“經典”的)C++書籍。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -