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