Go 語言設計失誤,缺乏遠見?
本文轉載自微信公眾號「腦子進煎魚了」,作者陳煎魚 。轉載本文請聯系腦子進煎魚了公眾號。
大家好,我是煎魚。
前段時間我有一個朋友在某站點上摸魚時,給我甩來一個主題為《golang 設計者是如何償還技術債的》鏈接。
說是讓我學習、圍觀一下社區觀點,早日好修成正果,本魚表示滿臉問號。
原回答如下圖:
主要是以極短的話語表述 Go 語言的 “泛型、異常、channel、annotation、模塊依賴” 的設計是失誤的。
說是沒有向各種編程語言的 “最佳實踐” 各取所需。
那些故事
剛好煎魚也入門 Go 沒幾天,偶爾翻過 issues 和 proposal,看了一點點歷史事件。
圖來自 Introduction to Golang
也從我的觀點來圍觀一下 Go 官方這些年為特性掙扎過的那些事。
涉及:
- 泛型。
- 錯誤處理。
- 依賴管理。
- 注解。
泛型
為什么 Go 語言這么久都沒有泛型,是不是 Go 官方不夠 “聰明”,抄作業都不會抄。這顯然是不對的。
有如下幾點原因:
- 泛型本質上并不是絕對的必需品。
- 泛型不是 Go 語言的早期目標。
- 其他 feature 更重要,把精力放在這些上面,Go 團隊人力很有限的。
歷史嘗試
在以往的嘗試中,Go 團隊有人進行過不少的泛型 proposal 試驗?;緯r間線(via @changkun)如下:
簡述 | 時間 | 作者 |
---|---|---|
Type Functions | 2010年 | Ian Lance Taylor |
Generalized Types | 2011年 | Ian Lance Taylor |
Generalized Types v2 | 2013年 | Ian Lance Taylor |
Type Parameters | 2013年 | Ian Lance Taylor |
go:generate | 2014年 | Rob Pike |
First Class Types | 2015年 | Bryan C.Mills |
Contracts | 2018年 | Ian Lance Taylor, Robert Griesemer |
Contracts | 2019年 | Ian Lance Taylor, Robert Griesemer |
Redundancy in Contracts(2019)'s Design | 2019年 | Ian Lance Taylor, Robert Griesemer |
Constrained Type Parameters(2020, v1) | 2020年 | Ian Lance Taylor, Robert Griesemer |
Constrained Type Parameters(2020, v2) | 2020年 | Ian Lance Taylor, Robert Griesemer |
Constrained Type Parameters(2020, v3) | 2020年 | Ian Lance Taylor, Robert Griesemer |
我們觀察一下,10 年過去了,Ian Lance Taylor 依然在開展泛型提案,持續地在思考著 Go 泛型。
堅持思考,這一點值得我們學習。
下一步計劃
在 2021 年尾巴的我們,明年(2022年) Go1.18 左右就可以見到 Go 泛型,基本跑不了。
在出來前可以看看《Go 1.17 支持泛型了?具體怎么用》,可以作為玩具用了。
接下來可以預見泛型出來后,一堆工具庫和數據結構很大可能會被逐步改寫,像是《Go 提案:增加泛型版 slices 和 maps 新包》,早已摩拳擦掌。
屆時 Go 源碼類別的書的部分內容也會失時效,需要關注 Go 版本的時效性。
錯誤處理
在日常工程中,我們寫的、看到最多的可能就是這一段標志性 Go 代碼:
- func main() {
- x, err := foo()
- if err != nil {
- // handle error
- }
- y, err := foo()
- if err != nil {
- // handle error
- }
- z, err := foo()
- if err != nil {
- // handle error
- }
- s, err := foo()
- if err != nil {
- // handle error
- }
- }
這是在業內被吐槽的最多的,甚至都可以用來作為 Gopher 的互認。
設計方向
那 Go 是瞎設計的嗎,就粗制濫造,搞個錯誤 err 的返回約定慣例。像是:
- func foo() err {
- return nil
- }
其實并不是,Go 團隊在設計上有意識地選擇了顯式的設計方向,如下:
- 使用顯式錯誤結果。
- 使用顯式錯誤檢查。
這和其他語言不一樣 ,是由于 Go 團隊也認識到了異常處理的不可見錯誤檢查所帶來的問題。
設計草案有一部分是受到了這些問題的啟發。如下:
目前 Go 官方也沒有打算去掉 “顯式” 這一做法,新版 Go2 錯誤處理的核心目標是:“錯誤檢查更加輕便,減少專門用于錯誤檢查的 Go 程序代碼的數量和所花費的時間。”。
從 Go2 的趨勢來看,主要是增加關鍵字和修飾來解決這個問題,相當于是堆積木了,而不是直接把他干掉的。
這在 Go 核心團隊內是非常明確的。
依賴管理
Go 語言在一開始是完全基于 GOPATH 作為依賴管理的模式,當時也鬧了不少的爭議出來。有以下核心問題:
依賴要手動拉取和下載,沒有強版本化的概念,開發者很難受(例如:不兼容升級、要拉取同一份)。
依賴和工程代碼必須在 GOPATH 下才能運行,不能任意擺放。
所以在 Go1.0~Go1.11 中,各路神仙發招,社區出現了各種諸如 dep、glide、godep 等依賴包管理工具。
時間線
后續 Go 團隊在 Russ Cox 的強勢推進下,力排眾議,推動 Go modules 的發展:
時間線如下:
- Go1.11 起開始推進 Go modules(前身 vgo)。
- Go1.13 起不再推薦使用 GOPATH 的使用模式。
- Go1.14 表示已經準備好,可以用在生產上(ready for production)了。
為什么這么晚
為什么 Go modules 這么晚才誕生,這是不是就是 Go 團隊的設計失誤呢?
我認為,是也不是。
Go 的誕生一開始是為了解決 Google 幾位大佬自己的痛點。
在 Google 的依賴管理上,本身是大倉庫(Monorepo)的模式,企業內部有自己一整套工具和流程,設計之初沒有這塊的強訴求。
如下:
圖來自 Mono Repo vs Multi Repo
有興趣的讀者詳細可閱讀《Why Google Stores Billions of Lines of Code in a Single Repository》,
Go 在社區開源后,大規模使用后這個問題就爆發了,社區自行釋出了方案??上?,五花八門,也都沒有解決好。官方隊伍就自己上手了。
要知道,沒有技術方案是完美的。Go modules 也被不少人所吐槽,存在爭議。
注解
Go 開發者中有大部分同學都有其他語言的使用經驗。在其他語言中,注解是一個強大的工具,沒得用會很不習慣。
圖片來自網絡
甚至有聽過沒有注解,就自嘲不會 “寫” 代碼了,所以一上來就找 Go 語言的注解怎么用了。
一些疑惑
我有一個朋友,經常會聽到如下疑惑,甚至無奈的發問:
“怎么樣在函數前聲明,直接開啟事務?”
"為什么 Java 可以完美注解,Go 就不行,難以理解,我無法接受..."
“那 Go 支持什么程度的注解?”
Go 的 “注解” 支撐的非常有限,基本都是 //go build、go:generate 這類輔助,達不到標準的裝飾器的作用。
為什么不支持
沒有全面的支持注解來做裝飾器,顯然不算 Go 的設計失誤,這是刻意為之,這是與錯誤處理的設計理念相關聯。
Go issues 上有人提過類似的提案:
Go Contributor @ianlancetaylor 給出了明確的答復,Go在設計上更傾向于明確的、顯式的編程風格。
優缺點如下:
- 優勢:不知道 Go 能從添加裝飾器中得到什么好處,沒能在 issues 上明確論證。
- 缺點:是明確的,會存在意外設置的情況。
因如下原因,沒有接受注解:
- 對比現有代碼方法,這種裝飾器的新的方法沒有提供比現有方法更多的優勢,大到足矣推翻原有的設計思路。
- 社區內的投票,支持的也很少(基于表情符號的投票),用戶反饋不多。
可能有小伙伴會說了,有注解做裝飾器了,代碼會簡潔不少。
但其實 Go 團隊的態度很明確:
Go 認為可讀性更重要,如果只是額外多寫一點代碼,在權衡后,還是可以接受的。
償還的過程
如果是在職場中工作多年的小伙伴,其實不難發現 Go 的發展史和業務的發展節奏是類似的。
在社區中吐槽的主要是兩塊,如下:
- 為什么這個功能不如此設計?
- 這個功能為什么沒有支持?
不如此設計
為什么 Go 語言不如此設計?經典的像是 Go 的錯誤處理(error),很多小伙伴會先入為主,以其他語言的最佳實踐,要教 Go 團隊設計,要 throw,要 catch!
其實想一下,我們做一個業務,這個業務就是 Go 語言。我們需要先做業務建模,確定 Go 的核心思想,才能持續的迭代和設計。
Go 語言的設計定義很明顯是:既要簡單、還要顯式,不能有隱式、要避免復雜,所以社區傳遞的是 “less is more” 的設計理念。
這么想,很多提案的落地,被拒等,都能了解到 Go 語言的設計哲學和團隊理念。
還沒有支持
為什么 Go 語言的 XXX 功能沒有支持?經典的像是 Go 的泛型、注解等功能。
還沒有支持的可能性有三點,如下:
- 還沒有想清楚。
- 早就被拒絕了。
- 優先級不夠高。
實際上和我們業務迭代一樣,Go 團隊的人力資源有限,做事會有優先級。前文所提到的 Russ Cox 就是現在 Go 團隊 Leader,每年也會開相關的會議討論事項。
像是 Go 泛型,顯然沒有,也不會影響到 Go 在業務初期的短期發展,國內依然存有一定的占用率。2011 年沒有想清楚,也就一直持續思考和嘗試了...
而注解,或是你們想到的。很多在 go issues 其實早就被拒絕過多次,自然還沒有支持,也是因為他不大可能直接出現了。
推進的模式
Go 在推進或償還新技術改進時,現在采取的模式都是一樣的。會先設計一個編譯時可以指定的 “變量”。
例如:
- 泛型的 G 變量。
- Modules 的 GO111MODULE 變量。
再在 Go 的不斷迭代中,推進使用和反饋,再推進變量的默認開啟,逐漸去除。
可以參考 GO111MODULE 的過程。
總結
我們在學習很多語言、技能時,會以既有的知識去認知,再對新的對象建立新的認知樹,很容易會有先入為主的認知行為。
但若沒有及時思考,就很容易產生偏見。認為 XXX 是 XXX,你 Go 語言就應該是 XXX,這樣是有失偏頗的。
就像我們行業經常討論的,網上的 A 同學,35 歲被裁員了。那你我,35 歲就 100% 會下崗嗎?
相反,Go 語言這 10+ 年來,基于自己的設計理念。保持了大致一貫的 less is more 設計理念,是值得贊許的。
我們要知道軟件設計,是沒有銀彈的。Go 語言的設計理念,有好有壞,社區也有不少人對大道至簡的理念嗤之以鼻。