Go 設計哲學:少即是多,哪裡來的?

煎魚發表於2022-03-10

大家好,我是煎魚。

之前在 Go 社群分享知識和經驗時,經常會聽見諸如:less is more、少即是多,大道至簡、大道不停地至簡等黑話。

甚至討論 Go issues 和提案時,都會有人用 “less is more” 來反駁或做為論點支撐,非常的有意思。大家都會很好奇,出處是哪裡,是什麼意思?

金句出處

如此根深蒂固的社群文化觀念,想必是有核心的人提出。說出這句話的作者就是他:

有人認得嗎?

他就是 Go 語言之父的 Rob Pike。

Rob Pike 在多個場合提到過類似 “少即是多” 的觀點,該觀點廣為流傳。

分享的場合諸如:

  • 在 2012 年,Go SF 會議上分享的是《Less is exponentially more》,這是最早的公開觀點整理而成的文章分享。
  • 在 2015 年,dotGo 會議上分享的是《Simplicity is Complicated》,持續不斷地灌輸文化,分享觀點。

該觀點的各類變型語句在業內廣為流傳,形成了 Go 社群獨特的 “文化”。

當然,從社群反響來看,有褒有貶。

演講內容

此處引用 Rob Pike 的《Less is exponentially more》,正文部分 @MIKESPOOK 翻譯,我這裡重新整理、修剪、引用、配圖,就不重複造輪子了。

如下圖:

背景

這是我(下指 Rob Pike)在 2012 年六月,舊金山 Go 會議上的演講內容。

這是一個私人演講。我並未代表 Go 專案團隊的任何人在此演講,但我首先要感謝團隊為 Go 的誕生和發展所做的一切。

同時,我也要感謝舊金山 Go 社群給我這個演講機會。

Go 讓我最驚訝的地方

在幾個星期之前我被問到:“在推出 Go 之後,什麼令你感到最為驚奇?”

我立刻有了一個答案:儘管我們希望 C++ 程式設計師來了解 Go 並作為一個可選的語言,但是更多的 Go 程式設計師來自如於 Python、Ruby,只有很少來自 C++。

我們(Ken,Robert 和我)自己曾經是 C++ 程式設計師,我們設計新的語言是為了解決那些我們編寫的軟體中遇到的問題。

而這些問題,其他 C++ 程式設計師似乎並不怎麼在意,這看起來有些矛盾。

為什麼要開發 Go

今天我想要談談是什麼促使我們建立了 Go,以及為什麼本不應該是這樣會我們驚訝的結果。

我承諾討論 Go 會比討論 C++ 多,即便你不瞭解 C++ 也仍然完全跟得上主題。

答案可以概括為:你認為少既是多,還是少就是少

貝爾實驗室的故事

這裡有一個真實的故事作為隱喻。如下:

  1. 貝爾實驗室最初用三個數字標識:111 表示物理研究,127 表示電腦科學研究,等等。
  2. 在上世紀八十年代早期,一篇如期而至的備忘錄宣告由於我們所瞭解的研究正在增長,為了便於識別我們的工作,必須再新增一位數。因此,我們的中心變為 1127。
  3. Ron Hardin 開玩笑半認真的說道,如果我們真的更好的瞭解了這個世界,我們可以減少一位數,使得 127 僅為 27。

當然管理層沒有聽到這個笑話,又或者他們不願意聽到,但是我想這其中確有大的智慧。少既是多。你理解得越好,越含蓄。

請務必記住這個思路。

開發 Go 的背景

C++ 編譯等待

回到 2007 年 9 月,我在一個巨大的 Google C++ 程式(就是你們都用過的那個)上做一些瑣碎但是很核心的工作。

我在那個巨大的分散式叢集上需要花大約 45 分鐘進行編譯。

C++ 新特性改進

收到一個通知說幾個受僱於 Google 的為 C++ 標準化委員會工作的人將會做一場報告。

他們將向我們介紹那時還被稱作 C++0x(就是現在眾所周知的 C++11)中將會有哪些改進。

在長達一個小時的報告中,我們聽說了諸如有已經在計劃中的 35 個特性之類的事情。

事實上有更多,但僅有 35 個特性在報告中進行了描述。當然一些特性很小,但是意義重大,值得在報告中提出。

還有一些非常微妙和難以理解,如:

  • 左右值引用(rvalue references)。
  • 可變引數模板(variadic templates。
  • 使用者定義資料標識(user-defined literals)。

這時我問了自己一個問題:C++ 委員會真得相信 C++ 的問題在於沒有足夠的特性

肯定的說,在另一個 Ron Hardin 的玩笑中,簡化語言的成就遠遠大於新增功能。當然這有點可笑,不過請務必記住這個思路。

實驗性語言嘗試

就在這個 C++ 報告會的數月前,我自己也進行了一場演講,你可以在 YouTube 上看到,關於我在上世紀 80 年代開發的一個玩具性質的併發語言。這個語言被叫做 Newsqueak,它是 Go 的前輩了。

我進行這次報告是因為在 Newsqueak 中缺失的一些想法,在為 Google 工作的時候我再次思考了這些它們。我當時確信它們可以使得編寫服務端程式碼變得更加輕鬆,使得 Google 能從中獲得收益。

事實上我曾嘗試在 C++ 中實現這些思路,但是失敗了。要將 C++ 控制結構和併發操作聯絡起來太困難了,最終這導致很難看到真正的優勢。

雖然我承認我從未真正熟練的使用 C++,但是純粹的 C++ 仍然讓所有事情看起來過於笨重。所以我放棄了這個想法。

但是那場 C++0x 報告讓我再次思考這個問題。有一件令我十分困擾的事情(同時我相信也在困擾著 Ken 和 Robert)是新的 C++ 記憶體模型有原子型別。

感覺上在一個已經負擔過重的型別系統上加入如此微觀的描述細節的集合是絕對的錯誤。這同樣是目光短淺的,幾乎能確信硬體在接下來的十年中將迅速發展,將語言和當今的硬體結合的過於緊密是非常愚蠢的。

Go 初始團隊

在報告後我們回到了辦公室。我啟動了另一個編譯,將椅子轉向 Robert,然後開始溝通關鍵的問題。

在編譯結束前,我們已經把 Ken 拉了進來,並且決定做些什麼。

我們不準備繼續寫 C++ 了,並且我們——尤其是我,希望在寫 Google 程式碼的時候能夠做輕鬆的編寫併發。

同時我們也想勇往直前的駕馭“大程式設計”,後面會談到。

Go 特性討論

我們在白板上寫了一堆想要的東西,和其必要條件。忽略了語法和語義細節,設想了藍圖和全域性。

我這裡還有那時的一個令人神混魂顛倒的郵件。

這裡摘錄了一部分:

  • Robert:起點是 C,修復一些明顯的缺陷,移除雜物,新增一些缺失的特性。
  • Rob:命名為 “go”。你們可以編造這個名字的來由,不過它有很好的底子。它很短,容易拼寫。工具:goc, gol, goa。如果有互動式偵錯程式/直譯器,可以就叫做 “go”。副檔名是 .go。
  • Robert:空介面為 interface{}。它們實現了所有的介面,所以這個可以用來代替 void *。

我們並沒有正確描繪全部的東西。例如,描繪 array 和 slice 用了差不多一年的時間。但是這個語言特色的大多數重要的東西都在開始的幾天裡確定下來。

注意 Robert 說 C 是起點,而不是 C++。我不確定,不過我相信他是指 C,尤其是 Ken 在的情況下。

不過事實是,最終我們沒有從 C 作為起點。我們從頭開始,僅僅借鑑瞭如運算子、括號、大括號、和部分關鍵字。(當然也從我們知道的其他語言中吸取了精髓。)

無論如何,我們現在同 C++ 做著相反的事情,解構全部,回到原點重新開始。我們並未嘗試去設計一個更好的 C++,甚至更好的 C。僅僅是一個對於我們在意的那種型別的軟體來說更好的語言。

最終,它成為了一個與 C 和 C++ 完全不同的語言。每個釋出版本都越來越不同。

Go 特性清單

我製作了一個 Go 中對 C 和 C++ 進行的重要簡化的清單:

  • 規範的語法(無需用於解析的符號表)。
  • 垃圾收集(唯一)。
  • 沒有標頭檔案。
  • 明確依賴
  • 無迴圈依賴。
  • 常量只能為數字。
  • int 和 int32 是不同的型別。
  • 字母大小寫設定可見性。
  • 任何型別都可以有方法(沒有類)。
  • 沒有子型別繼承(沒有子類)。
  • 包級別初始化和定義好的初始化順序。
  • 檔案編譯到一個包中。
  • 包級別的全域性表達與順序無關。
  • 沒有算術轉換(常量做了輔助處理)。
  • 隱式的介面實現(無需“implements”定義)。
  • 嵌入(沒有向父類的升級)。
  • 方法如同函式一樣進行定義(沒有特的別位置要求)。
  • 方法就是函式。
  • 介面僅僅包含方法(沒有資料)。
  • 方法僅通過名字匹配(而不是通過型別)。
  • 沒有構造或者析構方法。
  • 後自增和後自減是語句,而不是表示式。
  • 沒有前自增或前自減。
  • 賦值不是表示式。
  • 按照賦值、函式呼叫定義時的順序執行(沒有“sequence point”)。
  • 沒有指標運算。
  • 記憶體總是零值初始化。
  • 對區域性變數取地址合法。
  • 方法沒有“this”。
  • 分段的堆疊。
  • 沒有靜態或其他型別註解。
  • 沒有模板。
  • 沒有異常。
  • 內建 string、slice、map。
  • 陣列邊界檢查。

除了這個簡化清單和一些未提及的瑣碎內容,我相信,Go 相比 C 或者 C++ 是更加有表達力的。少既是多。

但是即便這樣也不能丟掉所有東西。仍然需要構建型別工作的方式,在實踐中恰當的語法,以及讓庫的互動更好這種令人感到忌諱不可言喻的事情。

我們也新增了一些 C 或者 C++ 沒有的東西,例如 slice 和 map,複合宣告,每個檔案的頂級表示式(一個差點被忘記的重要東西),反射,垃圾收集,等等。當然,還有併發。

無法想象沒有泛型

當然明顯缺少的是型別層次化。請允許我對此爆那麼幾句粗口。

在 Go 最初的版本中,有人告訴我他無法想像用一個沒有泛型範型的語言來工作。就像之前在某些地方提到過的,我認為這絕對是神奇的評論。

公平的說,他可能正在用其自己的方式來表達非常喜歡 STL 在 C++ 中為他做的事情。在辯論的前提下,讓我們先相信他的觀點。

他說編寫像 int 列表或 map string 這樣的容器是一個無法忍受的負擔。我覺得這是個神奇的觀點。

即便是那些沒有泛型範型的語言,我也只會花費很少的時間在這些問題上。

物件導向的方式

但是更重要的是,他說型別是放下這些負擔的解決途徑。型別。不是函式多型,不是語言基礎,或者其他協助,僅僅用型別。

這就是卡住我的細節問題。

從 C++ 和 Java 轉過來 Go 的程式設計師懷念工作在型別上的程式設計方式,尤其是繼承和子類,以及所有相關的內容。可能對於型別來說,我是門外漢,不過我真得從未發現這個模型十分具有表達力。

我已故的朋友 Alain Fournier 有一次告訴我說他認為學術的最低階形式就是分類。那麼你知道嗎?型別層次化就是分類。

你必須對哪塊進哪個盒子作出決策,包括每個型別的父級,不論是 A 繼承自 B,還是 B 繼承自 A。

一個可排序的陣列是一個排序過的陣列還是一個陣列表達的排序器?如果你堅信所有問題都是由型別驅動設計的,那麼你就必須作出決策。

我相信這樣思考程式設計是荒謬可笑的。核心不是東西之間的祖宗關係,而是它們可以為你做什麼。

當然,這就是介面進入 Go 的地方。但是它們已經是藍圖的一部分,那是真正的 Go 哲學。

如果說 C++ 和 Java 是關於型別繼承和型別分類的,Go 就是關於組合的。

Unix pipe 的最終發明人 Doug McIlroy 在 1964 (!) 這樣寫到:

我們應當像連線花園裡的龍頭和軟管一樣,用某種方式一段一段的將訊息資料連線起來。這同樣是 IO 使用的辦法。

這也是 Go 使用的辦法。Go 用了這個主意,並且將其向前推進了一大步。這是一個關於組合與連線的語言。

一個顯而易見的例子就是介面為我們提供的組合元件的方式。只要它實現了方法 M,就可以放在合適的地方,而不關心它到底是什麼東西。

另一個重要的例子是併發如何連線獨立執行的計算。並且也有一個不同尋常(卻非常簡單)的型別組合模式:嵌入。

這就是 Go 特有的組合技術,滋味與 C++ 或 Java 程式完全不同。

C++/Java 的大程式設計模式

有一個與此無關的 Go 設計我想要提一下:Go 被設計用於幫助編寫大程式,由大團隊編寫和維護。

有一個觀點叫做“大程式設計”,不知怎麼回事 C++ 和 Java 主宰了這個領域。我相信這只是一個歷史的失誤,或者是一個工業化的事故。但是一個廣泛被接受的信念是物件導向的設計可以做些事情。

我完全不相信那個。大軟體確實需要方法論保駕護航,但是用不著如此強的依賴管理和如此清晰的介面抽象,甚至如此華麗的文件工具,但它不比強大的依賴管理、清晰的介面抽象和優秀的文件工具來得更重要,而這些沒有一樣是 C++ 做好的事情(儘管 Java 明顯做得更好一些)。

我們還不知道,因為沒有足夠的軟體採用 Go 來編寫,不過我有自信 Go 將在大程式設計領域脫穎而出。時間證明一切。

為什麼 Go 不被 C++ 程式設計師喜歡

現在,回到我演講一開始提到的那個令人驚奇的問題:

為什麼 Go,一個被設計為用於摧毀 C++ 的語言,併為並未獲得 C++ 程式設計師的芳心?

撇開玩笑不說,我認為那是因為 Go 和 C++ 有著完全不同的哲學。

C++ 是讓你的指尖解決所有的問題

我在 C++11 的 FAQ 上引用了這段內容:

C++ 與那些巨大增長的特別編寫的手工程式碼相比,具有更加廣泛的抽象,優雅、靈活並且零成本的表達能力。

這個思考的方向與 Go 的不同。零成本不是目標,至少不是零 CPU 成本。Go 的主張更多考慮的是最小化程式設計師的工作量

Go 不是無所不包的。你無法通過內建獲得所有東西。你無法精確控制每個細微的執行。例如沒有 RAII。而可以用垃圾收集作為代替。也沒有記憶體釋放函式。

你得到的是功能強大,但是容易理解的,容易用來構建一些用於連線組合解決問題的模組

這可能最終不像你使用其他語言編寫的解決方案那麼快,那麼精緻,在思想體系上那麼明確,但它確實會更加容易編寫,容易閱讀,容易理解,容易維護,並且更加安全。

換句話說,當然,有些過於簡單:

  • Python 和 Ruby 程式設計師:轉到 Go 是因為他們並未放棄太多的表達能力,但是獲得了效能,並且與併發共舞。
  • C++ 程式設計師:無法轉到 Go 是因為他們經過艱辛的戰鬥才獲得對其語言的精確控制能力,而且也不想放棄任何已經獲得的東西。對於他們,軟體不僅僅是關於讓工作完成,而是關於用一個確定的方式完成。

那麼問題是,Go 的成功能否反駁他們的世界觀?我們應當在一開始的時候就意識到了一點。

那些為 C++11 的新特性而興奮的人們是不會在意一個沒有這麼多特性的語言。即便最後發現這個語言能夠比他們所想象的提供更多。

謝謝大家。

總結

一直以來對 Go 的哲學 “less is more” 非常的好奇,來源是何處,含義又是什麼?

在春節期間閱讀和梳理了一遍,雖然演講內容比較多,也偏向口語化。但本質上 Rob Pike 所說的 “less is more” 是一個比較有趣的東西。

核心觀點在於:“Go 與 C++ 的觀念完全不同,希望程式設計師的工作量最小化,自身少量的特性,應當能夠連線組合解決問題,更具表達力,而不是堆功能”。

你覺得呢?:)

若有任何疑問歡迎評論區反饋和交流,最好的關係是互相成就,各位的點贊就是煎魚創作的最大動力,感謝支援。

文章持續更新,可以微信搜【腦子進煎魚了】閱讀,本文 GitHub github.com/eddycjy/blog 已收錄,學習 Go 語言可以看 Go 學習地圖和路線,歡迎 Star 催更。

推薦閱讀

相關文章