計算機語言的鉅變

Linux愛好者2018-04-13 10:29:58

(點擊上方公眾號,可快速關注)


英文:Eric Raymond,翻譯:Linux中國/name1e5s

linux.cn/article-9503-1.html


我的上一篇博文《與 C 語言長別離》引來了我的老朋友,一位 C++ 專家的評論。在評論裏,他推薦把 C++ 作為 C 的替代品。這是不可能發生的,如果 C++ 代替 C 是趨勢的話,那麼 Go 和 Rust 也就不會出現了。



但是我不能只給我的讀者一個光禿禿的看法(LCTT 譯註:此處是雙關語)。所以,在這篇文章中,我來講述一下為什麼我不再碰 C++ 的故事。這是關於計算機語言設計經濟學專題文章的起始點。這篇文章會討論為什麼一些真心不好的決策會被做出來,然後進入語言的基礎設計之中,以及我們該如何修正這些問題。


在這篇文章中,我會一點一點的指出人們(當然也包括我)自從 20 世紀 80 年代以來就存在的關於未來的編程語言的預見失誤。直到最近,我們才找到了證明我們錯了的證據。


我記得我第一次學習 C++ 是因為我需要使用 GNU eqn 輸出 MathXML,而 eqn 是使用 C++ 寫的。那個項目不錯。在那之後,21 世紀初,我在韋諾之戰Battle For Wesnoth那邊當了多年的資深開發人生,並且與 C++ 相處甚歡。


在那之後啊,有一天我們發現一個不小心被我們授予提交權限的人已經把遊戲的 AI 核心搞崩掉了。顯然,在團隊中只有我是不那麼害怕查看代碼的。最終,我把一切都恢復正常了 —— 我折騰了整整兩週。再那之後,我就發誓我再也不靠近 C++ 了。


在那次經歷過後,我發現這個語言的問題就是它在嘗試使得本來就複雜的東西更加複雜,來粗陋補上因為基礎概念的缺失造成的漏洞。對於裸指針這樣東西,它説“別這樣做”,這沒有問題。對於小規模的個人項目(比如我的魔改版 eqn),遵守這些規定沒有問題。


但是對於大型項目,或者開發者水平參差不齊的多人項目(這是我經常要處理的情況)就不能這樣。隨着時間的推移以及代碼行數的增加,有的人就會捅簍子。當別人指出有 BUG 時,因為諸如 STL 之類的東西給你增加了一層複雜度,你處理這種問題所需要的精力就比處理同等規模的 C 語言的問題就要難上很多。我在韋諾之戰時,我就知道了,處理這種問題真的相當棘手。


我給 Stell Heller(我的老朋友,C++ 的支持者)寫代碼時不會發生的問題在我與非 Heller 們合作時就被放大了,我和他們合作的結局可能就是我得給他們擦屁股。所以我就不用 C++ ,我覺得不值得為了其花時間。 C 是有缺陷的,但是 C 有 C++ 沒有的優點 —— 如果你能在腦內模擬出硬件,那麼你就能很簡單的看出程序是怎麼運行的。如果 C++ 真的能解決 C 的問題(也就是説,C++ 是類型安全以及內存安全的),那麼失去其透明性也是值得的。但是,C++ 並沒有這樣。


我們判斷 C++ 做的還不夠的方法之一是想象一個 C++ 已經搞得不錯的世界。在那個世界裏,老舊的 C 語言項目會被遷移到 C++ 上來。主流的操作系統內核會是 C++ 寫就,而現存的內核實現,比如 Linux 會漸漸升級成那樣。在現實世界,這些都沒有發生。C++ 不僅沒有打消語言設計者設想像 D、Go 以及 Rust 那樣的新語言的想法,它甚至都沒有取代它的前輩。不改變 C++ 的核心思想,它就沒有未來,也因此,C++ 的抽象泄露leaky abstraction也不會消失。


既然我剛剛提到了 D 語言,那我就説説為什麼我不把 D 視為一個夠格的 C 語言競爭者的原因吧。儘管它比 Rust 早出現了八年(和 Rust 相比是九年)Walter Bright 早在那時就有了構建那樣一個語言的想法。但是在 2001 年,以 Python 和 Perl 為首的語言的出現已經確定了,專有語言能和開源語言抗衡的時代已經過去。官方 D 語言庫/運行時和 Tangle 的無謂紛爭也打擊了其發展。它從未修正這些錯誤。


然後就是 Go 語言(我本來想説“以及 Rust”。但是如前文所述,我認為 Rust 還需要幾年時間才能有競爭力)。它的確是類型安全以及內存安全的(好吧,是在大多數時候是這樣,但是如果你要使用接口的話就不是如此了,但是自找麻煩可不是正常人的做法)。我的一位好友,Mark Atwood,曾指出過 Go 語言是脾氣暴躁的老頭子因為憤怒而創造出的語言,主要是 C 語言的作者之一(Ken Thompson) 因為 C++ 的混亂臃腫造成的憤怒,我深以為然。


我能理解 Ken 惱火的原因。這幾十年來我就一直認為 C++ 搞錯了需要解決的問題。C 語言的後繼者有兩條路可走。其一就是 C++ 那樣,接受 C 的抽象泄漏、裸指針等等,以保證兼容性。然後以此為基礎,構建一個最先進的語言。還有一條道路,就是從根源上解決問題 —— 修正 C語言的抽象泄露。這一來就會破環其兼容性,但是也會杜絕 C/C++ 現有的問題。


對於第二條道路,第一次嚴謹的嘗試就是 1995 年出現的 Java。Java 搞得不錯,但是在語言解釋器上構建這門語言使其不適合系統編程。這就在系統編程那留下一個巨大的洞,在 Go 以及 Rust 出現之前的 15 年裏,都沒有語言來填補這個空白。這也就是我的 GPSD 和 NTPsec 等軟件在 2017 年仍然主要用 C 寫成的原因,儘管 C 的問題也很多。


在許多方面這都是很糟糕的情況。儘管由於缺少足夠多樣化的選擇,我們很難認識到 C/C++ 做的不夠好的地方。我們都認為在軟件裏面出現缺陷以及基於安全方面考慮的妥協是理所當然的,而不是想想這其中多少是真的由於語言的設計問題導致的,就像緩存區溢出漏洞一樣。


所以,為什麼我們花了這麼長時間才開始解決這個問題?從 C 1972 年面世到 Go 2009 年出現,這其中隔了 37 年;Rust 也是在其僅僅一年之前出現。我想根本原因還是經濟。


從最早的計算機語言開始,人們就已經知道,每種語言的設計都體現了程序員時間與機器資源的相對價值的權衡。在機器這端,就是彙編語言,以及之後的 C 語言,這些語言以犧牲開發人員的時間為代價來提高性能。 另一方面,像 Lisp 和(之後的)Python 這樣的語言則試圖自動處理儘可能多的細節,但這是以犧牲機器性能為代價的。


廣義地説,這兩端的語言的最重要的區別就是有沒有。這與經驗一致,內存管理缺陷是以機器為中心的語言中最常見的一類缺陷,程序員需要手動管理資源。


當相對價值斷言與軟件開發在某個特定領域的實際成本動因相匹配時,這個語言就是在經濟上可行的。語言設計者通過設計一個適合處理現在或者不遠的將來出現的情況的語言,而不是使用現有的語言來解決他們遇到的問題。


隨着時間的推移,時興的編程語言已經漸漸從需要手動管理內存的語言變為帶有自動內存管理以及垃圾回收(GC)機制的語言。這種變化對應了摩爾定律導致的計算機硬件成本的降低,使得程序員的時間與之前相比更加的寶貴。但是,除了程序員的時間以及機器效率的變化之外,至少還有兩個維度與這種變化相關。


其一就是距離底層硬件的距離。底層軟件(內核與服務代碼)的低效率會被成倍地擴大。因此我們可以發現,以機器為中心的語言向底層推進,而以程序員為中心的語言向着高級發展。因為大多數情況下面向用户的語言僅僅需要以人類的反應速度(0.1 秒)做出迴應即可。


另一個維度就是項目的規模。由於程序員抽象發生的問題的漏洞以及自身的疏忽,任何語言都會有可預期的每千行代碼的出錯率。這個比率在以機器為中心的語言上很高,而在程序員為中心的帶有 GC 的語言裏就大大降低。隨着項目規模的增大,帶有 GC 的語言作為一個防止出錯率不堪入目的策略就顯得愈發重要起來。


當我們使用這三種維度來看當今的編程語言的形勢 —— C 語言在底層,蓬勃發展的帶有 GC 的語言在上層,我們會發現這基本上很合理。但是還有一些看似不合理的是 —— C 語言的應用不合理地廣泛。


我為什麼這麼説?想想那些經典的 Unix 命令行工具吧。那些小程序通常都可以使用帶有完整的 POSIX 支持的腳本語言快速實現出來。重新編碼那些程序將使得它們調試、維護和拓展起來都會更加簡單。


但是為什麼還是使用 C (或者某些像 eqn 的項目,使用 C++)?因為有轉換成本。就算是把相當小、相當簡單的程序使用新的語言重寫並且確認你已經忠實地保留了所有非錯誤行為都是相當困難的。籠統地説,在任何一個領域的應用編程或者系統編程在一種語言的權衡過時之後,仍然堅持使用它。


這就是我和其他預測者犯的大錯。 我們認為,降低機器資源成本(增加程序員時間的相對成本)本身就足以取代 C 語言(以及沒有 GC 的語言)。 在這個過程中,我們有一部分或者甚至一大部分都是錯誤的 —— 自 20 世紀 90 年代初以來,腳本語言、Java 以及像 Node.js 這樣的東西的興起顯然都是這樣興起的。


但是,競爭系統編程語言的新浪潮並非如此。 Rust 和 Go 都明確地迴應了增加項目規模 這一需求。 腳本語言是先是作為編寫小程序的有效途徑,並逐漸擴大規模,而 Rust 和 Go 從一開始就定位為減少大型項目中的缺陷率。 比如 Google 的搜索服務和 Facebook 的實時聊天覆用。


我認為這就是對 “為什麼不再早點兒” 這個問題的回答。Rust 和 Go 實際上並不算晚,它們相對迅速地迴應了一個直到最近才被發現低估的成本動因問題。


好,説了這麼多理論上的問題。按照這些理論我們能預言什麼?它告訴我們在 C 之後會出現什麼?


推動 GC 語言發展的趨勢還沒有扭轉,也不要期待其扭轉。這是大勢所趨。因此:最終我們將擁有具有足夠低延遲的 GC 技術,可用於內核和底層固件,這些技術將以語言實現方式被提供。 這些才是真正結束 C 長期統治的語言應有的特性。


我們能從 Go 語言開發團隊的工作文件中發現端倪,他們正朝着這個方向前進 —— 可參見關於併發 GC 的學術研究 —— 從未停止研究。 如果 Go 語言自己沒有選擇這麼做,其他的語言設計師也會這樣。 但我認為他們會這麼做 —— 谷歌推動他們的項目的能力是顯而易見的(我們從 “Android 的發展”就能看出來)。


在我們擁有那麼理想的 GC 之前,我把能替換 C 語言的賭注押在 Go 語言上。因為其 GC 的開銷是可以接受的 —— 也就是説不只是應用,甚至是大部分內核外的服務都可以使用。原因很簡單: C 的出錯率無藥可醫,轉化成本還很高。


上週我嘗試將 C 語言項目轉化到 Go 語言上,我發現了兩件事。其一就是這活很簡單, C 的語言和 Go 對應的很好。還有就是寫出的代碼相當簡單。由於 GC 的存在以及把集合視為首要的數據結構,人們會預期代碼減少,但是我意識到我寫的代碼比我最初期望的減少的更多,比例約為 2:1 —— 和 C 轉 Python 類似。


抱歉吶,Rust 粉們。你們在內核以及底層固件上有着美好的未來,但是你們在別的 C 領域被 Go 壓的很慘。沒有 GC ,再加上難以從 C 語言轉化過來,還有就是 API 的標準部分還是不夠完善。(我的 select(2) 又哪去了啊?)。


對你們來説,唯一的安慰就是,C++ 粉比你們更糟糕 —— 如果這算是安慰的話。至少 Rust 還可以在 Go 顧及不到的 C 領域內大展宏圖。C++ 可不能。



看完本文有收穫?請分享給更多人

關注「Linux 愛好者」,提升Linux技能

淘口令複製以下紅色內容,再打開手淘即可購買

範品社,使用¥極客T恤¥搶先預覽(長按複製整段文案,打開手機淘寶即可進入活動內容)

閲讀原文

TAGS: