文/雲風
語言之爭永遠是火藥味十足的話題。尤其是 C 和 C++ 的目標市場又有很高的重合性,C++ 程序員往往對C++ 其有著宗教般的虔誠。我想,今天我在自己的 blog 上繼續這個戰爭,一定會換來更多的罵名。只不過這次 Linus 幾句話真是說到我心坎裡去了,不喊出來會憋壞的 :D
首先,給不熟悉我的朋友做一個技術背景的自我介紹:
我不是一個 Linux 的 fans ,雖然我今天對 Windows 也沒有什麼好感,但我的大部分工作還是在 Windows 上做應用軟件開發的,對 Windows 還算熟悉。現在我也用非 Windows 的系統,但那是一台 FreeBSD 的機器,不是 Linux 。
我自認為對 C++ 相當熟悉,精讀過市面上能買到的關於 C++ 的大部分書籍,像 D&E of C++ 這樣的經典還讀了不只一遍。用 C++ 寫過至少數十萬行代碼,閱讀過 STL 的大部分源碼,和 ACE / Boost 的一小部分。
曾經我是 C++ 的忠實粉絲,如果誰說 C++ 的不是,要麼會選擇跟他辯論到底,要麼會對此人不屑一顧。
還有一點我認為非常重要:我第一次愛上 C++ 是 15 年前(1992 年),然後對其慢慢冷淡,回歸 C 的懷抱。而到了 2000 年,我又一次愛上 C++ 。也就是說,從熱愛 C++ 到否定它,在我的個人經歷中,有過兩次。不排除未來有第三次的可能,但這一點足可說明,否定 C++ 是出於一種理性的判斷,而不是一種沖動。
寫上這些,並非是想倚老賣老。我知道,想罵我的 C++ 程序員,更討厭有人倚老賣老的數落 C++ 的不是。而且論資格,我頂多及的上 Linus 大大的一個零頭,既然有老人在前撐腰,下面說話的底氣就可以足一些了 :)
C 是 C++ 的一個子集(從 C99 開始已經不是了),用 C 能寫出來的代碼,C++ 一樣可以寫出來,然後可以完成的更好。
這是新手們自以為是的攻擊武器。Linus 用了一個很恰當的理由做出反擊:“你當然可以用任何語言編寫糟糕的代碼。但是,有些語言,尤其是帶有一些心智(mental)包袱的語言本身就非常糟糕。”
沒錯,我最想說的就是這個。C++ 就是一個“帶有一些心智(mental)包袱的語言”。這對軟件設計的影響非常之大,沒有經年的軟件開發實踐很難理解這一點。
從這一點上展開,把 ASM 和 C 比較的問題和 C 與 C++ 的比較相提並論就沒有意義了。
接下來要找到的問題要點就是,C++ 比 C 多出來那些東西後,真的會帶來心智包袱嗎?這個問題不好回答。單純從 C++ 語言特性的繁雜導致的不易掌握和誤用這些角度是很難說服我自己的,更別說去說服那些比我聰明的多,刻苦的多的 C++ 程序員們。我自認為對所謂 C++ 的高級特性掌握的還是不錯的,並運用在諸多實際項目中。他們相當有趣,在某種程度上也非常的有效。代碼可以獲得相當高的執行效率,並可以縮短編碼的時間(更少的鍵擊數),完成他們也有很大的成就感。
好了,讓我再引用 Linus 的一句說到我心坎裡的話。“字符串/內存管理根本無關緊要。這不是重要的部分,而且也不復雜。唯一真正重要的部分是設計。”
設計!這才是重中之重。
如果要說,這最近 10 年的程序員生涯我學會了什麼?我認為,我比以前能設計出更好的代碼了。能更准確的把握設計的壞味道。而對編程語言的掌握,對操作系統的熟悉,工作相關知識的了解等等。那些只是自然而然發生的事,那些是知識的積累,而非能力的提高。
“抽象”,“面向對象”,“設計模式”,這些重要嗎?重要。對軟件開發相當重要。但重要不是必要,執迷於“抽象”會使你離目標越來越遠。當我們一次又一次的提取出事物的共性,建立起抽象層的時候,我們可能丟棄了真實。C++ 繼承了 C 語言中“信任程序員”這一設計哲學,致力於讓程序員在建立抽象層時,可以不做出額外的消耗。他的解決方式是提供盡可能多的語言工具和設計選擇,任何一個都允許你在不用的時候不帶來額外的性能損失。
這是一個美好的願景:C++ 程序員指望可以建立強大的可復用的抽象層,面對世界上一切的具體應用。同時 CPU 執行序列在穿越這個堅厚的抽象層的過程中,居然可以以光速通過(通過抽象層沒有額外的執行效率付出)。為此:C++ 社區創造了 STL ,創造了 Boost 。它們共同的關鍵詞是:效率、復用。
再往上呢?另一個問題產生了:“——低效的抽象編程模型,可能在兩年之後你會注意到有些抽象效果不怎麼樣,但是所有代碼已經依賴於圍繞它設計的‘漂亮’對象模型了,如果不重寫應用程序,就無法改正。”這一段依舊是 Linus 語,我不停的引用,是因為我明白這一點,但是不能表達的更清楚。
使用 C++ 的程序員不斷的強調復用性,卻不斷的需要重寫代碼。如果一段代碼可以不被重寫,那多半是因為對重寫工程量的妥協。是的,其實我們可以用 C++ 的各種特性寫出更好,更漂亮,更高效的代碼。兩年前的框架不那麼完美,不是 C++ 語言的錯,是兩年前的我能力有限的緣故。但是因為需要改寫的是設計框架,這意味著我們必須跟著變更已經完成的功能模塊,或是加上橋接層。
的確,STL 和 Boost 都是世界頂尖程序員完成的。代碼質量非常的高(當然,我對 Boost 的一部分持保留意見)。我不拿編譯器兼容性和可移植性或是編譯速度說事,雖然這些的確是現實問題,但不足以成為反對 C++ 基礎類庫的理由。
好好的用好 C++ 當然得用好 STL ,Boost 也應該認真考察一下。能夠仔細讀一下源碼更好。合格的 C++ 程序員應該做這個。否則作為 C++ 程序員你就違背了 C++ 語言的設計哲學:C++ 信任了你,你就該對的起這種信任,搞清楚你寫的每一行代碼背後,機器都去干了什麼。
但是,STL 過於龐大了,Boost 更加是。我不是抱怨閱讀和學習它們的源碼的難度和需要的時間和精力。正相反,我在學習它們的過程中充滿了樂趣和感激之情。高手前輩透過這些高質量的代碼教會了我很多東西。我隱隱擔心的是,這麼龐大的代碼,它的設計不可能是永遠正確的。兩年之後,他們的設計肯定依舊正確,再兩年還是的。但是我幾乎敢肯定,放之更長遠的時間來看,絕對會在某些設計領域發現其不是最佳的選擇。到那一天,我們會選擇修改嗎?我想 C++ 社區會被迫選擇妥協。但是,C++ 程序員心中會充滿痛苦。
C 在這個問題上的抉擇是不一樣的。在效率問題上,C 程序裡最令人擔心的是函數調用的消耗。C++ 程序員最津津樂道的案例就是 std::sort 全面擊敗了 C 庫中的 qsort 。C 語言的失敗正在於多余的函數調用消耗。
但是,從一開始 C 就選擇了承認函數調用的消耗,而這一點幾乎是唯一。付出了這個代價後,設計失誤導致的效率下降問題幾乎總可以避免。C 和 C++ 都可以選擇重寫設計失敗的部分,但不一樣的是, C 程序員幾乎可以不考慮妥協的問題。同樣的是考慮極端效率的語言,C 語言坦然面對缺陷,才是真正的符合了 KISS 原則。
我對這個問題的見解,可以再引用 Linus 的一段話作為收場。“如果你想用更花哨的語言,C++絕對是最糟糕的選擇。如果想要真正的高級特性,那就選擇有垃圾回收或者好的系統集成的,而不是既缺乏C的簡約(sparseness)又缺乏C的直接而且沒有重要概念的高層綁定(high-level bindings to important concepts)的東西。”。這是我最近幾年來一直堅持的觀點:C++ 的發展,一定要補充對 GC 支持所需要的特性。
強調一下,我並不討厭 C++ :) 。 C++ 的粉絲們可以隨便罵我,但是不要帶上階級仇恨。
ps. 最近兩年多,我在做一個游戲引擎的項目。這個項目現在是第三個版本了。第一個版本是用 C++ 實現的,但是沒有用任何已存在的類庫(包括 STL)。在第二個版本中,我去掉了所有使用 C++ 高級特性實現的部分,只使用了 C++ 基本特性實現所有。今年重寫的第三個版本,全部換成 C 代碼了。這個項目的發展,可以反應出我個人對 C/C++ 理解的心路過程。