天天看點

“Go 語言的優點、缺點和平淡無奇之處”的十年

作者:CSDN
“Go 語言的優點、缺點和平淡無奇之處”的十年

【編者按】本文作者對他在十年前撰寫的一篇名為 “Go 語言:優點、缺點和平淡無奇之處” 的文章進行回顧和更新,讨論了他的準确預測、Go 語言的變化以及他之前的疏漏。本文見證了 GO 語言這十年的演進曆程。

原文連結:https://blog.carlmjohnson.net/post/2023/ten-years-of-go-good-bad-meh/

未經允許,禁止轉載!

作者 | Carl M. Johnson 譯者 | 明明如月

責編 | 夏萌出品 | CSDN(ID:CSDNnews)

十年前,我撰寫了一篇名為Go 語言:優點、缺點和平淡無奇之處(連結見文末)的文章。該篇文章在 2013 年沖上了Hacker News 的首頁,并在/r/programming 上 收獲了超過 400 條評論。盡管我未留下當時的分析資料,但我推測這應該是我最受關注的文章之一,而且這絕對是我第一次在寫作中收獲大量回報的難忘經曆。

如今,十年過去了。在這段時間裡,我從一個出于娛樂心态隻是試驗 Go 語言的業餘愛好者,轉變為一名将 Go 列為主要程式設計語言的專業程式員。是以,我覺得現在回顧并分析我當年的觀點,探讨我準确預測了什麼、什麼事情發生了改變,以及我忽視和犯下了哪些錯誤,這會是一次有趣的經曆。你可以閱讀或重新閱讀原始博文,或者隻需在這裡閱讀我對過去的回顧,無需深入解析那篇文章。你隻需要知道,正如它的标題所示,我當時把我對 Go 語言的評價分為“優點”、“缺點”和“平淡無奇之處”。

對于任何希望将本文送出至社交媒體的讀者,請注意,标題中的引号非常重要。切勿删除它們。

“Go 語言的優點、缺點和平淡無奇之處”的十年

我的準确預測

我仍然認同我在"優點"部分列出的大部分内容。以下所有觀點我都依然認為是對的:

  • Go 語言是為了在擁有現代版控系統的團隊中開發大型項目而設計的
  • 通過大寫字母來區分函數、方法、變量和字段的公有/私有狀态
  • 将目錄作為包管理的基本單元
  • 使用單一的二進制檔案進行部署
  • go 工具運作速度快,内置了 go fmt、go doc 和 go test
  • 使用後置類型樣式(var x int)而非前置類型樣式(int x)
  • 有明确的變量聲明(相對于 Python 的 = 進行隐式聲明)
  • Go Playground
  • 邏輯類型名稱(如 int64,float32 等,而非 long 和 double)
  • 提供三種基本資料類型:字元串、變長數組和哈希映射
  • 接口用于編譯時的鴨子類型 - 不支援繼承

至于其他部分,我們将在後續進行讨論。

發生的變化

過去十年中,Go 語言的最顯著變化無疑是引入了泛型。

在我之前的文章中,我把缺乏泛型列為了 Go 語言的一大弊端:

在 Go 代碼中,我們常會使用一些特殊的技巧,通過接口來避免使用泛型。但在某些情況下,你可能别無選擇,隻能将函數複制粘貼多次,以針對不同類型進行操作。如果你需要建構自己的資料結構,你可能會選擇 interface {} 作為通用類型,但這會喪失編譯時的類型安全性。反之,如果你想實作一個通用的 sum 函數,就沒有理想的解決方案。

在 2022 年 2 月釋出的 Go 1.18 版本中,引入了泛型特性。這基本上解決了我以前的遇到問題。例如,一個泛型的 sum 函數會像下面這樣:

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 系統是 Go 語言的一大優點:

如果你運作 go get github.com/userA/project/ ,它将使用 git 從 github.com 下載下傳項目,并自動将其放置到正确的位置。如果該項目中包含行import "bitbucket.com/userB/library" ,go 工具也能夠下載下傳并安裝這個庫到指定位置。是以,Go 語言一舉具備了自身的優雅打包解決方案和一個現代化的、去中心化的 CPAN 或 PyPI :你已經在使用的版本控制系統!

Go modules 在原有的系統基礎上,增加了指定導入包以及 Go 本身的版本需求的功能。雖然在從 GOPATH 切換到 Go modules 的過程中出現了一些波折和不滿,但總的來說,過渡非常順利,就我個人而言,我并未遇到太多實際問題。它的工作方式很有效,大多數時候你無需過多考慮它。

在我之前的文章中,我提到對于 Go 編譯器隻産生錯誤而不産生警告的情況我并未有太大的感觸。但過去十年中,這個問題已經演變為 go vet 可以産生低誤報率的錯誤,而其他警告則可以通過各種 lint 工具産生。是以,從技術角度來說,編譯器依然沒有警告,但在實際使用中,Go 的生态系統确實提供了很多産生警告的途徑。這隻是在你想要批評 Go 時,才會成為一個有趣的差異。

“Go 語言的優點、缺點和平淡無奇之處”的十年

我的疏漏之處

在回顧我先前的文章時,我注意到并未涉及聯合類型、交叉類型或者可選值這些概念。這反映出在過去的十年中,整個行業的狀況變化劇烈。在當時,和類型及可選值仍被視為學術性的特性,除了 Haskell 這樣的 ML 衍生語言,主流的 C 影響語言并沒有涵蓋這些特性。然而,自從 2014 年 Swift 和 2015 年 Rust 的誕生以來, 所有的語言都開始根據其是否解決了"空指針錯誤"(被比喻為十億美元的錯誤)進行評價。

可惜的是,Go語言已經有太多使用 nil 值的代碼,是以很難去掉 nil 值,以至于我們無法真正轉向可選類型,至少在我們所了解的 Go 語言中是如此。然而,有一個通過限制接口值來添加求和類型 的提法,我認為這有很大的可能以某種形式實作。

值得一提的是,對語言的評價已經發生了變化。我曾經贊賞 Go 的類型推斷為技術進步,而現在 Hacker News 的使用者則認為 Go 的類型推斷與其他語言相比還比較弱。選擇一個不為人知的語言,或者堅持足夠久,總會有一天你會成為别人诟病的目标。

再回首過去,我發現我原文中還有一個疏漏是沒有提到 Go 1 保證。Go 1.0 釋出時,Go 團隊對源代碼的相容性做出了承諾。盡管一直都有警告、錯誤和例外存在,但在大多數情況下,他們極大地遵守了這個承諾。公正地說,當時,Go 1.0.3 是最新的版本,是以我無法預見 Go 1 的承諾将會持續十年以上,但我相信這對 Go 成為今天的語言發揮了關鍵作用。在 Go 1.0 之前,Go 團隊經常對語言或标準庫做出改變,為了跟上新标準,需要在代碼庫上運作 go fix。然而,自那時以來,隻要你寫的是 Go 程式,使用的是标準庫,并且沒有依賴于不安全的特性或者安全漏洞,你就能保證你的代碼一直正常運作。

相比其他生态系統,這是一股真正的清新之風,作為開發人員,這是我最喜歡的事情之一。在其他程式設計語言中,更新到一個新版本(即使是一個次要版本),往往讓人感到擔憂,擔心會有一些意想不到的問題出現。即使在更新過程中提前通過棄用警告聲明了問題,仍然需要花時間來修複相關問題。但是使用Go,我不會擔心這些。GO 語言團隊非常為開發者着想。

“Go 語言的優點、缺點和平淡無奇之處”的十年

我的觀點更新

總體而言,我認為我之前的博文品質不錯,沒有什麼明顯的錯誤。不過,雖然并不完全否定我之前的有些看法,但我現在有了更深入的了解。

具體而言,在我描述的“優點”部分,沒有什麼真正的不足之處,但回顧過去,我對并發帶來的權衡有了更深的認識。

我曾寫到:

Go 的并發處理方式,就如同你在剛開始學習并發時所設想的那樣,非常易于了解和使用。如果你想并發地運作一個函數,隻需要使用 go function(arguments)。如果你需要讓函數間進行通信,你可以使用通道,這些通道預設會同步執行,即在兩端都準備就緒前,會暫停執行。

這個觀點仍然是正确的,我依然認為在 Go 中處理并發比在 Python 中要簡單得多。(順便說一句,我寫下這些觀點是在 JavaScript 添加了 Promise 和 await 之前。)然而,類型系統并不能阻止你建立資料競争,是以在測試過程中你必須在依賴資料競争檢測器,如果你在不遵循公認的模式的情況下直接使用通道建立結構化并發,這會導緻代碼混亂。這是一種權衡,Go 給你充足的自由以至于可能自我陷入困境,但在大多數時候,當你隻是編寫網絡伺服器或類似的應用時,你可以獲得并發的大部分好處,而不會遇到太多的麻煩。

就我列出的“缺點”部分來說,大部分實際上并未給我帶來太大問題。我不确定為何我會過度擔憂字元串類型無法定義方法。腳本無法以 #! 開頭在實踐中其實并不重要。Go 的語言設計并非完全遵循 DRY (Don't repeat yourself,不要重複你自己)原則,但這更多的是一種權衡,而非“缺點”。

雖然缺乏泛型有時會導緻問題,但通常有明确的解決辦法,例如使用動态類型,使用反射,或者編寫一個代碼生成器。我很高興 Go 現在引入了泛型,但事實上,沒有泛型時我們隻會偶爾遇到真正的問題。我非常期待看到泛型如何影響 Go 的未來發展,尤其是在 Go 團隊正在研究疊代器的情況下,但我确實有些擔心可能會出現“泛型的濫用”,即在普通接口已經能夠很好工作的地方過度使用泛型。我們将拭目以待。

我在“無所謂”的部分列出的大部分事項,實際上更多的是權衡,然而事後看來,我認為 Go 在它所選擇的設計範圍内做出了更好的決定。沒有異常機制是一種權衡,雖然我可能希望 Go 有像 Zig 的 try 和 errdefer 那樣的功能,但實際上,目前的方式是可行的。盡管使用 if err != nil 是目前為止最差的選擇,除去所有其他不定期試驗的系統,但事實證明,它能促使使用者代碼有用的演變。

如 Matklad 在一條最近的評論中所說,

看來在錯誤處理上,我們(開發者社群)大緻達成了共識。Midori、Go、Rust、Swift、Zig 在設計上有着相似之處,這并非完全是檢查異常,但卻非常接近。

1、有一種标記函數可能會失敗的方式。這通常是傳回類型的屬性,而不是函數本身的屬性(Rust 中的 Result,Go 中的錯誤對,Zig 中的 ! 類型,以及 Midori 和 Swift 中的 bare throws decl)

2、我們不僅标記抛出異常的函數聲明,還會标記調用處(Midori、Swift、Zig 中的 try,Rust 中的 ?,Go 中的 if err != nil)。

3、預設存在一個 AnyError 類型(Go 中的 error,Swift 中的 Error,Rust 中的 anyhow,Zig 中的 anyerror)。一般來說,區分是否有錯誤的價值,大于詳盡地指定錯誤集合。

這種觀點對我來說是合理的。Go 的錯誤處理相比其他語言更為繁瑣,但從結構上來看,它們在底層有許多共同之處。

我曾把 Go 缺乏操作符重載、函數/方法重載或關鍵字參數等特性歸入“無所謂”的部分,但現在我對這種狀态感到非常滿意。我希望 Go 有操作符重載的唯一情況是 big.Int ,我希望有一天這些特性會被添加到語言本身中。關鍵字參數可能是好的,但實際上,結構體就夠用了,如果有真正棘手的情況,總是有 方法鍊的建構器可用。

“Go 語言的優點、缺點和平淡無奇之處”的十年

總結

在撰寫這篇文章前, 我原以為讀者們會關注我對面向對象程式設計的批評,然而,實際上他們更在意的是我用“白癡”一詞描述争論公有/私有字段問題的人。

我在文章中提出的最大疑問可能是 Go 語言對接口的運用以及繼承特性的缺失。我認為這是一個好主意,但也歡迎實踐的檢驗,并期待聽到不同的觀點。Hynek Schlawack 在 2021 年發表的一篇極佳的文章解釋了為什麼接口的運用以及沒有繼承可以被看作是 Go 的優秀設計選擇。簡單來說,隻有在子類覆寫超類方法的情況下,繼承才有意義。在這種情況下,Go 的類型嵌入表現得十分出色。是以,我終于激起了我期待已久的讨論,盡管這花了大約八年的時間。

直到 2019 年,Dan Abramov 才對 Hacker News 的寫作經驗進行了深入的解讀:

恭喜你!

你的項目成為了熱門新聞聚合器的頭條。社群裡的一些知名人士也在推特上提到了它。他們在說些什麼?

你感到心情沉重。

并不是說人們不喜歡這個項目。你清楚項目存在權衡,也期待人們會對此進行讨論。但是事情并未如預期發生。

然而,評論在很大程度上并不關心你的想法。

最熱門的評論主題是圍繞 README 示例中的編碼風格。它演變成了一個關于縮進的争論,有一百多條回複,并對不同的程式設計語言如何處理格式進行了簡短的曆史回顧。其中必然會提到 gofmt 和 Python。你使用過 Prettier 嗎?

你感到困惑,關閉了标簽頁。

發生了什麼?

也許是你的想法簡單地不像你想象的那樣有趣。也可能是你對偶然通路的人解釋得不夠好。

但是,你可能沒有得到相關回報的另一個原因。

我們更偏向于探讨那些相對容易讨論的話題。

我已經接受了這樣一個事實:論壇上的人們(包括我自己)更容易關注自己已經想到的事情,而非文章所讨論的主題。這就是現實,短期内恐怕無法改變。

我認為 Rachel 的 Run XOR Use 規則 (要麼使用一個聊天視窗或留言闆,但不能同時做兩者。這是為了避免沖突和分散注意力。)同樣适用于撰寫和讨論部落格文章。如果你釋出了文章,你必須預備好讨論會擴充到你自己都未曾預見的領域。

對于 Go 語言,我現在的滿意度超過了以往的任何時期。雖然它的速度并不如 Rust,但已經超過了我的需求,同時并無過度的模闆代碼或抽象概念誘惑。我認為 GO 語言團隊具有良好的洞察力,他們總是穩健地向正确的方向前進。我期待看到它未來的發展。

預測總是很難的,尤其是對于未來的事物。十年後我還會使用 Go 嗎?我無法确定。預期中的十年總似乎比回顧中的時間要長。也許那時我們隻是在檢查由 AI 生成的代碼的輸出,雖然這聽起來有些消極。但至少現在,我樂意将它作為我的主要程式設計語言。

以下是那篇文章的原始結論,以及我在 2023 年的更新:

Go 真的很棒,它已經成為我的日常語言(它曾經是 Python ),而且它絕對是一種趣味十足的語言,非常适合處理大型項目。如果你有興趣學習 Go,我建議學習下這個簡單的教程,然後在将測試程式輸入到 Playground 中運作,并且認真學習 GO 語言規範。該規範簡潔易讀,是學習 Go 非常好的學習材料。

讓我們在 2033 年再會,屆時我們将會在 “““Go 語言:優點、缺點和平淡無奇之處” 十年"十年” 中相見。

你也正在使用 GO 語言程式設計嗎?你認同作者的說法嗎?你對 GO 語言怎麼看?歡迎在評論區讨論。

參考連結:

  1. Go 語言:優點、缺點和平淡無奇之處:https://blog.carlmjohnson.net/post/google-go-the-good-the-bad-and-the-meh/
  2. Hacker News:https://news.ycombinator.com/item?id=5200916
  3. John Carmack 是否閱讀過這篇文章:https://twitter.com/ID_AA_Carmack/status/1293311943995002881
  4. 原始博文:https://blog.carlmjohnson.net/post/google-go-the-good-the-bad-and-the-meh/
  5. Go Playground:https://go.dev/play/
  6. Go 1.18 版本中:https://blog.carlmjohnson.net/post/2021/golang-118-minor-features/
  7. 下面這樣:https://go.dev/play/p/R-QDDqcp3w9
  8. 十億美元的錯誤:https://en.wikipedia.org/wiki/_pointer#History
  9. 通過限制接口值來添加求和類型:https://github.com/golang/go/issues/57644
  10. 選擇一個不為人知的語言,或者堅持足夠久,總會有一天你會成為别人诟病的目标:https://www.stroustrup.com/quotes.html
  11. Go 1 保證:https://go.dev/doc/go1compat
  12. Go 1.0.3 是最新的版本:https://go.dev/doc/devel/release#go1
  13. 公認的模式:https://blog.carlmjohnson.net/post/share-memory-by-communicating/
  14. 結構化并發:https://github.com/carlmjohnson/flowmatic
  15. 導緻代碼混亂:https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/
  16. 實踐中其實并不重要:https://github.com/carlmjohnson/go-run
  17. DRY:https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
  18. 使用者代碼有用的演變:https://blog.carlmjohnson.net/post/2020/working-with-errors-as/
  19. Matklad:https://matklad.github.io/
  20. 一條最近的評論:https://lobste.rs/s/aocv9o/trouble_with_checked_exceptions_2003#c_bsxqyu
  21. big.Int:https://pkg.go.dev/math/big#Int
  22. 添加到語言本身中:https://github.com/golang/go/issues/19624
  23. 方法鍊的建構器:https://blog.carlmjohnson.net/post/2021/requests-golang-http-client/
  24. 這篇文章:https://blog.carlmjohnson.net/post/google-go-the-good-the-bad-and-the-meh/
  25. 一篇極佳的文章:https://hynek.me/articles/python-subclassing-redux/
  26. 深入的解讀:https://overreacted.io/name-it-and-they-will-come/
  27. Run XOR Use 規則:https://rachelbythebay.com/w/2021/05/26/irc/
  28. 雖然這聽起來有些消極:https://blog.carlmjohnson.net/post/2016-04-09-alphago-and-our-dystopian-ai-future/
  29. 那篇文章:https://blog.carlmjohnson.net/post/google-go-the-good-the-bad-and-the-meh/
  30. 學習下這個簡單的教程:http://tour.golang.org/
  31. Playground:http://play.golang.org/
  32. GO 語言規範:http://golang.org/ref/spec

繼續閱讀