“Go 語言的優(yōu)點、缺點和平淡無奇之處”的十年(go 語言 優(yōu)勢及 主要用途)
【編者按】本文作者對他在十年前撰寫的一篇名為 “Go 語言:優(yōu)點、缺點和平淡無奇之處” 的文章進行回顧和更新,討論了他的準確預(yù)測、Go 語言的變化以及他之前的疏漏。本文見證了 GO 語言這十年的演進歷程。
原文鏈接:https://blog.carlmjohnson.net/post/2023/ten-years-of-go-good-bad-meh/
未經(jīng)允許,禁止轉(zhuǎn)載!
作者 | Carl M. Johnson 譯者 | 明明如月
責(zé)編 | 夏萌
出品 | CSDN(ID:CSDNnews)
十年前,我撰寫了一篇名為Go 語言:優(yōu)點、缺點和平淡無奇之處(鏈接見文末)的文章。該篇文章在 2013 年沖上了Hacker News 的首頁,并在/r/programming 上 收獲了超過 400 條評論。盡管我未留下當時的分析數(shù)據(jù),但我推測這應(yīng)該是我最受關(guān)注的文章之一,而且這絕對是我第一次在寫作中收獲大量反饋的難忘經(jīng)歷。
如今,十年過去了。在這段時間里,我從一個出于娛樂心態(tài)只是試驗 Go 語言的業(yè)余愛好者,轉(zhuǎn)變?yōu)橐幻麑?Go 列為主要編程語言的專業(yè)程序員。因此,我覺得現(xiàn)在回顧并分析我當年的觀點,探討我準確預(yù)測了什么、什么事情發(fā)生了改變,以及我忽視和犯下了哪些錯誤,這會是一次有趣的經(jīng)歷。你可以閱讀或重新閱讀原始博文,或者只需在這里閱讀我對過去的回顧,無需深入解析那篇文章。你只需要知道,正如它的標題所示,我當時把我對 Go 語言的評價分為“優(yōu)點”、“缺點”和“平淡無奇之處”。
對于任何希望將本文提交至社交媒體的讀者,請注意,標題中的引號非常重要。切勿刪除它們。
我的準確預(yù)測
我仍然認同我在”優(yōu)點”部分列出的大部分內(nèi)容。以下所有觀點我都依然認為是對的:
-
Go 語言是為了在擁有現(xiàn)代版控系統(tǒng)的團隊中開發(fā)大型項目而設(shè)計的
通過大寫字母來區(qū)分函數(shù)、方法、變量和字段的公有/私有狀態(tài)
將目錄作為包管理的基本單元
使用單一的二進制文件進行部署
go 工具運行速度快,內(nèi)置了 go fmt、go doc 和 go test
使用后置類型樣式(var x int)而非前置類型樣式(int x)
有明確的變量聲明(相對于 Python 的 = 進行隱式聲明)
Go Playground
邏輯類型名稱(如 int64,float32 等,而非 long 和 double)
提供三種基本數(shù)據(jù)類型:字符串、變長數(shù)組和哈希映射
接口用于編譯時的鴨子類型 – 不支持繼承
至于其他部分,我們將在后續(xù)進行討論。
發(fā)生的變化
過去十年中,Go 語言的最顯著變化無疑是引入了泛型。
在我之前的文章中,我把缺乏泛型列為了 Go 語言的一大弊端:
在 Go 代碼中,我們常會使用一些特殊的技巧,通過接口來避免使用泛型。但在某些情況下,你可能別無選擇,只能將函數(shù)復(fù)制粘貼多次,以針對不同類型進行操作。如果你需要構(gòu)建自己的數(shù)據(jù)結(jié)構(gòu),你可能會選擇 interface {} 作為通用類型,但這會喪失編譯時的類型安全性。反之,如果你想實現(xiàn)一個通用的 sum 函數(shù),就沒有理想的解決方案。
在 2022 年 2 月發(fā)布的 Go 1.18 版本中,引入了泛型特性。這基本上解決了我以前的遇到問題。例如,一個泛型的 sum 函數(shù)會像下面這樣:
type Numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float64 | ~float32
}
func sum[N Numeric](vals ...N) N {
var total N = 0
for _, val := range vals {
total = val
}
return total
}
到目前為止,泛型的引入無疑是一項重大的改進。
與 2013 年的 Go 語言相比,另一項重要變化是引入了 Go modules。我在過去的文章中提到,當時的 GOPATH 系統(tǒng)是 Go 語言的一大優(yōu)點:
如果你運行 go get github.com/userA/project/,它將使用 git 從 github.com 下載項目,并自動將其放置到正確的位置。如果該項目中包含行
import “bitbucket.com/userB/library”
,go 工具也能夠下載并安裝這個庫到指定位置。因此,Go 語言一舉具備了自身的優(yōu)雅打包解決方案和一個現(xiàn)代化的、去中心化的 CPAN 或 PyPI :你已經(jīng)在使用的版本控制系統(tǒng)!
Go modules 在原有的系統(tǒng)基礎(chǔ)上,增加了指定導(dǎo)入包以及 Go 本身的版本需求的功能。雖然在從 GOPATH 切換到 Go modules 的過程中出現(xiàn)了一些波折和不滿,但總的來說,過渡非常順利,就我個人而言,我并未遇到太多實際問題。它的工作方式很有效,大多數(shù)時候你無需過多考慮它。
在我之前的文章中,我提到對于 Go 編譯器只產(chǎn)生錯誤而不產(chǎn)生警告的情況我并未有太大的感觸。但過去十年中,這個問題已經(jīng)演變?yōu)?go vet 可以產(chǎn)生低誤報率的錯誤,而其他警告則可以通過各種 lint 工具產(chǎn)生。所以,從技術(shù)角度來說,編譯器依然沒有警告,但在實際使用中,Go 的生態(tài)系統(tǒng)確實提供了很多產(chǎn)生警告的途徑。這只是在你想要批評 Go 時,才會成為一個有趣的差異。
我的疏漏之處
在回顧我先前的文章時,我注意到并未涉及聯(lián)合類型、交叉類型或者可選值這些概念。這反映出在過去的十年中,整個行業(yè)的狀況變化劇烈。在當時,和類型及可選值仍被視為學(xué)術(shù)性的特性,除了 Haskell 這樣的 ML 衍生語言,主流的 C 影響語言并沒有涵蓋這些特性。然而,自從 2014 年 Swift 和 2015 年 Rust 的誕生以來, 所有的語言都開始根據(jù)其是否解決了”空指針錯誤”(被比喻為十億美元的錯誤)進行評價。
可惜的是,Go語言已經(jīng)有太多使用 nil 值的代碼,所以很難去掉 nil 值,以至于我們無法真正轉(zhuǎn)向可選類型,至少在我們所了解的 Go 語言中是如此。然而,有一個通過限制接口值來添加求和類型 的提法,我認為這有很大的可能以某種形式實現(xiàn)。
值得一提的是,對語言的評價已經(jīng)發(fā)生了變化。我曾經(jīng)贊賞 Go 的類型推斷為技術(shù)進步,而現(xiàn)在 Hacker News 的用戶則認為 Go 的類型推斷與其他語言相比還比較弱。選擇一個不為人知的語言,或者堅持足夠久,總會有一天你會成為別人詬病的目標。
再回首過去,我發(fā)現(xiàn)我原文中還有一個疏漏是沒有提到 Go 1 保證。Go 1.0 發(fā)布時,Go 團隊對源代碼的兼容性做出了承諾。盡管一直都有警告、錯誤和例外存在,但在大多數(shù)情況下,他們極大地遵守了這個承諾。公正地說,當時,Go 1.0.3 是最新的版本,因此我無法預(yù)見 Go 1 的承諾將會持續(xù)十年以上,但我相信這對 Go 成為今天的語言發(fā)揮了關(guān)鍵作用。在 Go 1.0 之前,Go 團隊經(jīng)常對語言或標準庫做出改變,為了跟上新標準,需要在代碼庫上運行 go fix。然而,自那時以來,只要你寫的是 Go 程序,使用的是標準庫,并且沒有依賴于不安全的特性或者安全漏洞,你就能保證你的代碼一直正常運行。
相比其他生態(tài)系統(tǒng),這是一股真正的清新之風(fēng),作為開發(fā)人員,這是我最喜歡的事情之一。在其他編程語言中,升級到一個新版本(即使是一個次要版本),往往讓人感到擔憂,擔心會有一些意想不到的問題出現(xiàn)。即使在升級過程中提前通過棄用警告聲明了問題,仍然需要花時間來修復(fù)相關(guān)問題。但是使用Go,我不會擔心這些。GO 語言團隊非常為開發(fā)者著想。
我的觀點更新
總體而言,我認為我之前的博文質(zhì)量不錯,沒有什么明顯的錯誤。不過,雖然并不完全否定我之前的有些看法,但我現(xiàn)在有了更深入的理解。
具體而言,在我描述的“優(yōu)點”部分,沒有什么真正的不足之處,但回顧過去,我對并發(fā)帶來的權(quán)衡有了更深的認識。
我曾寫到:
Go 的并發(fā)處理方式,就如同你在剛開始學(xué)習(xí)并發(fā)時所設(shè)想的那樣,非常易于理解和使用。如果你想并發(fā)地運行一個函數(shù),只需要使用 go function(arguments)。如果你需要讓函數(shù)間進行通信,你可以使用通道,這些通道默認會同步執(zhí)行,即在兩端都準備就緒前,會暫停執(zhí)行。
這個觀點仍然是正確的,我依然認為在 Go 中處理并發(fā)比在 Python 中要簡單得多。(順便說一句,我寫下這些觀點是在 JavaScript 添加了 Promise 和 await 之前。)然而,類型系統(tǒng)并不能阻止你創(chuàng)建數(shù)據(jù)競爭,因此在測試過程中你必須在依賴數(shù)據(jù)競爭檢測器,如果你在不遵循公認的模式的情況下直接使用通道創(chuàng)建結(jié)構(gòu)化并發(fā),這會導(dǎo)致代碼混亂。這是一種權(quán)衡,Go 給你充足的自由以至于可能自我陷入困境,但在大多數(shù)時候,當你只是編寫網(wǎng)絡(luò)服務(wù)器或類似的應(yīng)用時,你可以獲得并發(fā)的大部分好處,而不會遇到太多的麻煩。
就我列出的“缺點”部分來說,大部分實際上并未給我?guī)硖髥栴}。我不確定為何我會過度擔憂字符串類型無法定義方法。腳本無法以 #! 開頭在實踐中其實并不重要。Go 的語言設(shè)計并非完全遵循 DRY (Don’t repeat yourself,不要重復(fù)你自己)原則,但這更多的是一種權(quán)衡,而非“缺點”。
雖然缺乏泛型有時會導(dǎo)致問題,但通常有明確的解決辦法,例如使用動態(tài)類型,使用反射,或者編寫一個代碼生成器。我很高興 Go 現(xiàn)在引入了泛型,但事實上,沒有泛型時我們只會偶爾遇到真正的問題。我非常期待看到泛型如何影響 Go 的未來發(fā)展,尤其是在 Go 團隊正在研究迭代器的情況下,但我確實有些擔心可能會出現(xiàn)“泛型的濫用”,即在普通接口已經(jīng)能夠很好工作的地方過度使用泛型。我們將拭目以待。
我在“無所謂”的部分列出的大部分事項,實際上更多的是權(quán)衡,然而事后看來,我認為 Go 在它所選擇的設(shè)計范圍內(nèi)做出了更好的決定。沒有異常機制是一種權(quán)衡,雖然我可能希望 Go 有像 Zig 的 try 和 errdefer 那樣的功能,但實際上,當前的方式是可行的。盡管使用 if err != nil 是目前為止最差的選擇,除去所有其他不定期試驗的系統(tǒng),但事實證明,它能促使用戶代碼有用的演變。
如 Matklad 在一條最近的評論中所說,
看來在錯誤處理上,我們(開發(fā)者社區(qū))大致達成了共識。Midori、Go、Rust、Swift、Zig 在設(shè)計上有著相似之處,這并非完全是檢查異常,但卻非常接近。
1、有一種標記函數(shù)可能會失敗的方式。這通常是返回類型的屬性,而不是函數(shù)本身的屬性(Rust 中的 Result,Go 中的錯誤對,Zig 中的 ! 類型,以及 Midori 和 Swift 中的 bare throws decl)
2、我們不僅標記拋出異常的函數(shù)聲明,還會標記調(diào)用處(Midori、Swift、Zig 中的 try,Rust 中的 ?,Go 中的 if err != nil)。
3、默認存在一個 AnyError 類型(Go 中的 error,Swift 中的 Error,Rust 中的 anyhow,Zig 中的 anyerror)。一般來說,區(qū)分是否有錯誤的價值,大于詳盡地指定錯誤集合。
這種觀點對我來說是合理的。Go 的錯誤處理相比其他語言更為繁瑣,但從結(jié)構(gòu)上來看,它們在底層有許多共同之處。
我曾把 Go 缺乏操作符重載、函數(shù)/方法重載或關(guān)鍵字參數(shù)等特性歸入“無所謂”的部分,但現(xiàn)在我對這種狀態(tài)感到非常滿意。我希望 Go 有操作符重載的唯一情況是 big.Int ,我希望有一天這些特性會被添加到語言本身中。關(guān)鍵字參數(shù)可能是好的,但實際上,結(jié)構(gòu)體就夠用了,如果有真正棘手的情況,總是有 方法鏈的構(gòu)建器可用。
總結(jié)
在撰寫這篇文章前, 我原以為讀者們會關(guān)注我對面向?qū)ο缶幊痰呐u,然而,實際上他們更在意的是我用“白癡”一詞描述爭論公有/私有字段問題的人。
我在文章中提出的最大疑問可能是 Go 語言對接口的運用以及繼承特性的缺失。我認為這是一個好主意,但也歡迎實踐的檢驗,并期待聽到不同的觀點。Hynek Schlawack 在 2021 年發(fā)表的一篇極佳的文章解釋了為什么接口的運用以及沒有繼承可以被看作是 Go 的優(yōu)秀設(shè)計選擇。簡單來說,只有在子類覆寫超類方法的情況下,繼承才有意義。在這種情況下,Go 的類型嵌入表現(xiàn)得十分出色。因此,我終于激起了我期待已久的討論,盡管這花了大約八年的時間。
直到 2019 年,Dan Abramov 才對 Hacker News 的寫作經(jīng)驗進行了深入的解讀:
恭喜你!
你的項目成為了熱門新聞聚合器的頭條。社區(qū)里的一些知名人士也在推特上提到了它。他們在說些什么?
你感到心情沉重。
并不是說人們不喜歡這個項目。你清楚項目存在權(quán)衡,也期待人們會對此進行討論。但是事情并未如預(yù)期發(fā)生。
然而,評論在很大程度上并不關(guān)心你的想法。
最熱門的評論主題是圍繞 README 示例中的編碼風(fēng)格。它演變成了一個關(guān)于縮進的爭論,有一百多條回復(fù),并對不同的編程語言如何處理格式進行了簡短的歷史回顧。其中必然會提到 gofmt 和 Python。你使用過 Prettier 嗎?
…
你感到困惑,關(guān)閉了標簽頁。
發(fā)生了什么?
也許是你的想法簡單地不像你想象的那樣有趣。也可能是你對偶然訪問的人解釋得不夠好。
但是,你可能沒有得到相關(guān)反饋的另一個原因。
我們更偏向于探討那些相對容易討論的話題。
我已經(jīng)接受了這樣一個事實:論壇上的人們(包括我自己)更容易關(guān)注自己已經(jīng)想到的事情,而非文章所討論的主題。這就是現(xiàn)實,短期內(nèi)恐怕無法改變。
我認為 Rachel 的 Run XOR Use 規(guī)則 (要么使用一個聊天窗口或留言板,但不能同時做兩者。這是為了避免沖突和分散注意力。)同樣適用于撰寫和討論博客文章。如果你發(fā)布了文章,你必須預(yù)備好討論會擴展到你自己都未曾預(yù)見的領(lǐng)域。
對于 Go 語言,我現(xiàn)在的滿意度超過了以往的任何時期。雖然它的速度并不如 Rust,但已經(jīng)超過了我的需求,同時并無過度的模板代碼或抽象概念誘惑。我認為 GO 語言團隊具有良好的洞察力,他們總是穩(wěn)健地向正確的方向前進。我期待看到它未來的發(fā)展。
預(yù)測總是很難的,尤其是對于未來的事物。十年后我還會使用 Go 嗎?我無法確定。預(yù)期中的十年總似乎比回顧中的時間要長。也許那時我們只是在檢查由 AI 生成的代碼的輸出,雖然這聽起來有些消極。但至少現(xiàn)在,我樂意將它作為我的主要編程語言。
以下是那篇文章的原始結(jié)論,以及我在 2023 年的更新:
Go 真的很棒,它已經(jīng)成為我的日常語言(它曾經(jīng)是 Python ),而且它絕對是一種趣味十足的語言,非常適合處理大型項目。如果你有興趣學(xué)習(xí) Go,我建議學(xué)習(xí)下這個簡單的教程,然后在將測試程序輸入到 Playground 中運行,并且認真學(xué)習(xí) GO 語言規(guī)范。該規(guī)范簡潔易讀,是學(xué)習(xí) Go 非常好的學(xué)習(xí)材料。
讓我們在 2033 年再會,屆時我們將會在 “““Go 語言:優(yōu)點、缺點和平淡無奇之處” 十年”十年” 中相見。
你也正在使用 GO 語言編程嗎?你認同作者的說法嗎?你對 GO 語言怎么看?歡迎在評論區(qū)討論。
參考鏈接:
-
Go 語言:優(yōu)點、缺點和平淡無奇之處:https://blog.carlmjohnson.net/post/google-go-the-good-the-bad-and-the-meh/
Hacker News:https://news.ycombinator.com/item?id=5200916
John Carmack 是否閱讀過這篇文章:https://twitter.com/ID_AA_Carmack/status/1293311943995002881
原始博文:https://blog.carlmjohnson.net/post/google-go-the-good-the-bad-and-the-meh/
Go Playground:https://go.dev/play/
Go 1.18 版本中:https://blog.carlmjohnson.net/post/2021/golang-118-minor-features/
下面這樣:https://go.dev/play/p/R-QDDqcp3w9
十億美元的錯誤:https://en.wikipedia.org/wiki/_pointer#History
通過限制接口值來添加求和類型:https://github.com/golang/go/issues/57644
選擇一個不為人知的語言,或者堅持足夠久,總會有一天你會成為別人詬病的目標:https://www.stroustrup.com/quotes.html
Go 1 保證:https://go.dev/doc/go1compat
Go 1.0.3 是最新的版本:https://go.dev/doc/devel/release#go1
公認的模式:https://blog.carlmjohnson.net/post/share-memory-by-communicating/
結(jié)構(gòu)化并發(fā):https://github.com/carlmjohnson/flowmatic
導(dǎo)致代碼混亂:https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/
實踐中其實并不重要:https://github.com/carlmjohnson/go-run
DRY:https://en.wikipedia.org/wiki/Don’t_repeat_yourself
用戶代碼有用的演變:https://blog.carlmjohnson.net/post/2020/working-with-errors-as/
Matklad:https://matklad.github.io/
一條最近的評論:https://lobste.rs/s/aocv9o/trouble_with_checked_exceptions_2003#c_bsxqyu
big.Int:https://pkg.go.dev/math/big#Int
添加到語言本身中:https://github.com/golang/go/issues/19624
方法鏈的構(gòu)建器:https://blog.carlmjohnson.net/post/2021/requests-golang-http-client/
這篇文章:https://blog.carlmjohnson.net/post/google-go-the-good-the-bad-and-the-meh/
一篇極佳的文章:https://hynek.me/articles/python-subclassing-redux/
深入的解讀:https://overreacted.io/name-it-and-they-will-come/
Run XOR Use 規(guī)則:https://rachelbythebay.com/w/2021/05/26/irc/
雖然這聽起來有些消極:https://blog.carlmjohnson.net/post/2016-04-09-alphago-and-our-dystopian-ai-future/
那篇文章:https://blog.carlmjohnson.net/post/google-go-the-good-the-bad-and-the-meh/
學(xué)習(xí)下這個簡單的教程:http://tour.golang.org/
Playground:http://play.golang.org/
GO 語言規(guī)范:http://golang.org/ref/spec