你應當如何學習C++(以及程式設計)(rev#1)
你應當如何學習C++(以及程式設計)(rev#1)
By 劉未鵬(pongba)
,其實C++何嘗不是。坊間流傳的錯誤的C++學習方法一抓就是一大把。我自己在學習C++的過程中也,浪費了不少時間。
為什麼會存在這麼多錯誤認識?原因主要有三個,一是C++語言的細節太多。二是一些著名的C++書籍總在(不管有意還是無意)暗示語言細節的重要性和有趣。三是現代C++庫的開發哲學必須用到一些犄角旮旯的語言細節(但注意,是庫設計,不是日常程式設計)。這些共同塑造了C++社群的整體心態和哲學。
單是第一條還未必能夠成氣候,其它語言的細節也不少(儘管比起C++起來還是小巫見大巫),就拿javascript
因為沒有,人們用巨集加上預設模板引數來實現類似效果。因為沒有,人們用模板加上解構函式的細節來完成類似工作。因為沒有typeof,人們用模板超程式設計和巨集加上無盡的細節來實現目標… C++開發者們的DIY精神不可謂不強。
然而,如果僅僅是因為要開發優秀的庫,那麼涉及這些細節都還是情有可原的,至少在C++09出現並且編譯器廠商跟上之前,這些都還能說是不得已而為之。但我們廣大的C++程式設計師呢?大眾是容易被誤導的,我也曾經是。以為掌握了更多的語言細節就更牛,但實際卻是那些語言細節十有八九是平時程式設計用都用不到的。C++中眾多的細節雖然在庫設計者手裡面有其用武之地,但普通程式設計師則根本無需過多關注,尤其是沒有實際動機的關注。一般性的,以及基本的,乃至以及演算法設計。才是真正需要花時間掌握的東西。
學習最佳編碼實踐比學習C++更重要。看優秀的程式碼也比埋頭用差勁的編碼方式寫垃圾程式碼要有效。直接、清晰、明瞭、KISS地表達意圖比玩編碼花招要重要…
避免去過問任何語言細節,除非必要。這個必要是指在實際程式設計當中遇到問題,這樣就算需要過問細節,也是最省事的,懶惰者原則嘛。一個掌握了基本的程式設計理念並有較強學習能力的程式設計師在用一門陌生的語言程式設計時就算拿著那本語言的聖經從索引翻起也可以編出合格的程式來。不是指對每門語言都得十年,那一輩子才能學幾門語言哪,如果按字母順序學的話一輩子都別指望學到Ruby了;十年學習程式設計更不是指先把語言特性從粗到細全都吃透才敢下手程式設計,在實踐中提高才是最重要的。
至於這種摳語言細節的哲學為何能在社群裡面呈野火燎原之勢,就是一個心理學的問題了。想像人們在論壇上討論問題時,一個對語言把握很細緻的人肯定能夠得到更多的佩服,而由於論壇上的問題大多是小問題,所以解決實際問題的真正能力並不能得到顯現,也就是說,知識型的人能夠得到更多佩服,後者便成為動力和仿效的砝碼。然而真正的程式設計能力是與語言細節沒關係的,熟練運用一門語言能夠幫你最佳表達你的意圖,但熟練運用一門語言絕不意味著要把它的邊邊角角全都記住。懂得一些常識,有了程式設計的基本直覺,遇到一些細節錯誤的時候再去查書,是最節省時間的辦法。
C++的書,Bjarne的聖經《The C++ Programming Language》是高屋建瓴的。《大規模C++程式設計》是挺務實的。《Accelerated C++》是最佳入門的。《C++ Templates》是僅作參考的。《C++ Template Metaprogramming》是精力過剩者可以玩一玩的,普通程式設計師碰都別碰的。《ISO.IEC C++ Standard 14882》不是拿來讀的。,新書是絕對可以期待的。
P.S. 關於如何學習程式設計,上有許多精彩的文章:這裡,這裡,這裡,這裡…實際上,我建議你去把g9老大的blog翻個底朝天 :P
再P.S. 書單?我是遑於給出一個類似《C++初學者必讀》這種書單的。C++的書不計其數,被公認的好書也不勝列舉。只不過有些書容易給初學者造成一種錯覺,就是“學習C++就應該是這個樣子的”。比如有朋友提到的《高質量C/C++程式設計》,這本書有價值,但不適合初學者,初學者讀這樣的書容易一葉障目不見泰山。實際上,正確的態度是,細節是必要的。但細節是次要的。其實學習程式設計我覺得應該最先學習如何用偽碼錶達思想呢,君不見《Introduction to Algorithm》裡面的程式碼?《TAOCP》中的程式碼?哦,對了它們是自己建立的語言,但這種僅教學目的的語言的目的就是為了避免讓寫程式的人一開始就忘了寫程式是為了完成功能,以為寫程式就是和語言細節作鬥爭了。Bjarne說程式的正確性最重要,boost的編碼標準裡面也將正確性列在效能前面。
此外,一旦建立了正確的學習程式設計的理念,其實什麼書(只要不是太垃圾的)都有些用處。都當成參考書,用的時候從目錄或索引翻,基本就對了。
再再P.S. myan老大和g9老大都給出了許多精彩的見解。我不得不再加上一個P.S。具體我就不摘錄了,如果你讀到這裡,請務必往下看他們的評論。轉載者別忘了轉載他們的評論:-)
許多朋友都問我同一個問題,到底要不要學習C++。其實這個問題問得很沒有意義。“學C++”和“不學C++”這個二分法是沒意義的,為什麼?因為這個問題很表面,甚至很浮躁。重要的不是你掌握的語言,而是你掌握的能力,借用myan老大的話,“重要的是這個磨練過程,而不是結果,要的是你粗壯的腿,而不是你身上背的那袋鹽巴。”。此外學習C++的意義其實真的是醉翁之意不在酒,像C/C++這種系統級語言,在學習的過程中必須要涉及到一些底層知識,如記憶體管理、編譯連線系統、組合語言、硬體體系結構等等等等知識(注意,這不包括過分犄角旮旯的語言枝節)。這些東西也就是所謂的內功了(其實最最重要的內功還是長期學習所磨練出來的自學能力)。對此在《》裡面提到的漏洞抽象定律闡述得就非常漂亮。
所以,答案是,讓你成為高手的並不是你掌握什麼語言,精通C++未必就能讓你成為高手,不精通C++也未必就能讓你成為低手。我想大家都不會懷疑g9老大如果要抄起C++做一個專案的話會比大多數自認熟練C++的人要做得漂亮。所以關鍵的不是語言這個表層的東西,而是底下的本質矛盾。當然,不是說那就什麼語言都不要學了,按照一種曹操的邏輯,“天下語言,唯imperative與declarative耳”。C++是前者裡面最複雜的一種,支援最廣泛的程式設計正規化。借用當初數學系入學大會上一個老師的話,“你數學都學了,還有什麼不能學的呢?”。學語言是一個途徑,如果你把它用來磨練自己,可以。如果你把它用來作為學習系統底層知識的鑰匙,可以。如果你把它用來作為學習如何編寫優秀的程式碼,如何組織大型的程式,如何進行抽象設計,可以。如果掉書袋,光啃細節,我認為不可以(除非你必須要用到細節,像boost庫的coder們)。
然後再借用一下g9老大的《》中的話:
銀彈和我們的職業發展有什麼相干?很簡單:我們得把時間用於學習解決本質困難。新技術給高手帶來方便。菜鳥們卻不用指望被新技術拯救。沿用以前的比喻,一流的攝影師不會因為相機的更新換代而丟掉飯碗,反而可能借助先進技術留下傳世佳作。因為攝影的本質困難,還是攝影師的藝術感覺。熱門技術也就等於相機。不停追新,學習這個框架,那個軟體,好比成天鑽研不同相機的說明書。而熱門技術後的來龍去脈,才好比攝影技術。為什麼推出這個框架?它解決了什麼其它框架不能解決的問題?它在哪裡適用?它在哪裡不適用?它用了什麼新的設計?它改進了哪些舊的設計?和朋友聊天時提到Steve McConnell的《Professional Software Development》裡面引了一個調查,說軟體開發技術的半衰期20年。也就是說20年後我們現在知識裡一半的東西過時。相當不壞。朋友打趣道:“應該說20年後IT界一半的技術過時,我們學的過時技術遠遠超過這個比例。具體到某人,很可能5年他就廢了”。話雖悲觀,但可見選擇學習內容的重要性。學習本質技藝(技術遲早過時,技藝卻常用長新)還有一好處,就是不用看著自己心愛的技術受到挑戰的時候乾嚎。C/C++過時就過時了唄,只要有其它的系統程式設計語言。Java倒了就倒了唄,未必我不能用.NET?Ruby曇花一現又如何。如果用得不爽,換到其它動態語言就是了。J2EE被廢了又怎樣?未必我們就做不出分佈系統了?。
一句話,只有人是真正的銀彈。職業發展的目標,就是把自己變成銀彈。那時候,你就不再是人,而是人彈。
最後就以我在Bjarne的眾多訪談當中摘錄的一些關於如何學習C++(以及程式設計)的看法結束吧(沒空逐段翻譯了,只將其中我覺得最重要的幾段譯了一下,當然,其它也很重要,這些段落是在Bjarne的所有采訪稿中摘抄出來的,所以強烈建議都過目一下):
I suspect that people think too little about what they want to build, too little about what would make it correct, and too much about "efficiency" and following fashions of programming style. The key questions are always: "what do I want to do?" and "how do I know that I have done if?". Strategies for testing enters into my concerns from well before I write the firat line of code, and that despite my view that you have to write code very early - rather than wait until a design is complete.
譯:我感覺人們過多關注了所謂“效率”以及跟隨程式設計風格的潮流,卻嚴重忽視了本不該被忽視的問題,如“我究竟想要構建什麼樣的系統”、“怎樣才能使它正確”。最關鍵的問題永遠是:“我究竟想要做什麼?”和“如何才能知道我的系統是否已經完成了呢?”就拿我來說吧,我會在編寫第一行程式碼之前就考慮測試方案,而且這還是在我關於應當早於設計完成之前就進行編碼的觀點的前提之下。
Obviously, C++ is very complex. Obviously, people get lost. However, most peple get lost when they get diverted into becoming language lawyers rather than getting lost when they have a clear idea of what they want to express and simply look at C++ language features to see how to express it. Once you know data absreaction, class hierarchies (object-oriented programming), and parameterization with types (generic programming) in a fairly general way, the C++ language features fall in place.
譯:誠然,C++非常複雜。誠然,人們迷失其中了。然而問題是,大多數人不是因為首先對自己想要表達什麼有了清晰的認識只不過在去C++語言中搜尋合適的語言特性時迷失的,相反,大多數人是在不覺成為語言律師的路上迷失在細節的叢林中的。事實是,只需對資料抽象、類體系結構(OOP)以及引數化型別(GP)有一個相當一般層面的瞭解,C++紛繁的語言特性也就清晰起來了。
Well, I don't think I made such a trade-off. I want elegant and efficient code. Sometimes I get it. These dichotomies (between efficiency versus correctness, efficiency versus programmer time, efficiency versus high-level, et cetera.) are bogus.
I think the real problem is that "we" (that is, we software developers) are in a permanent state of emergency, grasping at straws to get our work done. We perform many minor miracles through trial and error, excessive use of brute force, and lots and lots of testing, but--so often--it's not enough.
Software developers have become adept at the difficult art of building reasonably reliable systems out of unreliable parts. The snag is that often we do not know exactly how we did it: a system just "sort of evolved" into something minimally acceptable. Personally, I prefer to know when a system will work, and why it will.
There are more useful systems developed in languages deemed awful than in languages praised for being beautiful--many more. The purpose of a programming language is to help build good systems, where "good" can be defined in many ways. My brief definition is, correct, maintainable, and adequately fast. Aesthetics matter, but first and foremost a language must be useful; it must allow real-world programmers to express real-world ideas succinctly and affordably.
I'm sure that for every programmer that dislikes C++, there is one who likes it. However, a friend of mine went to a conference where the keynote speaker asked the audience to indicate by show of hands, one, how many people disliked C++, and two, how many people had written a C++ program. There were twice as many people in the first group than the second. Expressing dislike of something you don't know is usually known as prejudice. Also, complainers are always louder and more certain than proponents--reasonable people acknowledge flaws. I think I know more about the problems with C++ than just about anyone, but I also know how to avoid them and how to use C++'s strengths.
In any case, I don't think it is true that the programming languages are so difficult to learn. For example, every first-year university biology textbook contains more details and deeper theory than even an expert-level programming-language book. Most applications involve standards, operating systems, libraries, and tools that far exceed modern programming languages in complexity. What is difficult is the appreciation of the underlying techniques and their application to real-world problems. Obviously, most current languages have many parts that are unnecessarily complex, but the degree of those complexities compared to some ideal minimum is often exaggerated.
We need relatively complex language to deal with absolutely complex problems. I note that English is arguably the largest and most complex language in the world (measured in number of words and idioms), but also one of the most successful.
C++ provides a nice, extended case study in the evolutionary approach. C compatibility has been far harder to maintain than I or anyone else expected. Part of the reason is that C has kept evolving, partially guided by people who insist that C++ compatibility is neither necessary nor good for C. Another reason-- probably even more important--is that organizations prefer interfaces that are in the C/C++ subset so that they can support both languages with a single effort. This leads to a constant pressure on users not to use the most powerful C++ features and to myths about why they should be used "carefully," "infrequently," or "by experts only." That, combined with backwards-looking teaching of C++, has led to many failures to reap the potential benefits of C++ as a high-level language with powerful abstraction mechanisms.
The question is how deeply integrated into the application those system dependencies are. I prefer the application to be designed conceptually in isolation from the underlying system, with an explicitly defined interface to "the outer world," and then integrated through a thin layer of interface code.
Had I had a chance to name the style of programming I like best, it would have been "class-oriented programming", but then I'm not particularly good at finding snappy names. The school of thought that I belong to - rooted in Simula and related design philosophies - emphasizes the role of compile-time checking and flexible (static) type systems. Reasoning about the behavior of a program has to be rooted in the (static) structure of the source code. The focus should be on guarantees, invariant, etc. which are closely tied to that static structure. This is the only way I know to effectively deal with correctness. Testing is essential but cannot be systematic and complete without a good internal program structure - simple-minded blackbox testing of any significant system is infeasible because of the exponential explosion of states.
So, I recommend people to think in terms of class invariants, exception handling guarantees, highly structured resource management, etc. I should add that I intensely dislike debugging (as ah hoc and unsystematic) and strongly prefer reasoning about source code and systematic testing.
Pros: flexibility, generality, performance, portability, good tool support, available on more platforms than any competitor except C, access to hardware and system resources, good availability of programmers and designers. Cons: complexity, sub-optimal use caused by poor teaching and myths.