Go語言之父的反思:我們做對了什么,做錯了什么
這是2023年11月10日我在悉尼GopherConAU 2023會議上的閉幕演講(視頻)[7],那一天也是Go開源14周年[8]的日子。本文中穿插著演示文稿中使用的幻燈片。
介紹
大家好!
首先,我要感謝Katie和Chewy讓我有幸為此次GopherConAU大會做閉幕演講。
2009年11月10日
今天是2023年11月10日,Go作為開源項目推出14周年的紀念日。
2009年11月10日那天,加州時間下午3點(如果沒記錯的話),Ken Thompson、Robert Griesemer、Russ Cox、Ian Taylor、Adam Langley、Jini Kim和我滿懷期待地看著網站上線。之后,全世界都知道我們在做什么了。
14年后的今天,有很多事情值得回顧。我想借此機會談談自那一天以來學到的一些重要經驗。即使是最成功的項目,在反思之后,也會發現一些事情本可以做得更好。當然,也有一些事情事后看來似乎是成功的關鍵所在。
首先,我必須明確的是,這里的觀點只代表我個人,不代表Go團隊和Google。無論是過去還是現在,Go都是由一支專注的團隊和龐大的社區付出巨大努力的結果。所以,如果你同意我的任何說法,請感謝他們。如果你不同意,請責怪我,但請保留你的意見。
鑒于本次演講的題目,許多人可能期待我會分析語言中的優點和缺點。當然,我會做一些分析,但還會有更多內容,原因有幾個。
首先,編程語言的好壞很大程度上取決于觀點而不是事實,盡管許多人對Go或任何其他語言的最微不足道的功能都存在爭論。
另外,關于換行符的位置、nil的工作方式、導出的大小寫表示法、垃圾回收、錯誤處理等話題已經有了大量的討論。這些話題肯定有值得討論的地方,但幾乎沒什么是還沒有被討論過的。
但我要討論的不僅僅是語言本身的真正原因是,語言并不是整個項目的全部。我們最初的目標不是創造一種新的編程語言,而是創造一種更好的編寫軟件的方式[9]。我們對所使用的語言有意見——無論使用什么語言,每個人都是如此——但是我們遇到的基本問題與這些語言的特性沒有太大關系,而是與在谷歌使用這些語言構建軟件的過程有關。
T恤上的第一只Gopher
新語言的創建提供了探索其他想法的新路徑,但這只是一個推動因素,而不是真正的重點。如果當時我正在工作的二進制文件不需要45分鐘來構建 ,Go語言就不會出現。但那45分鐘不是因為編譯器慢(因為它不慢),也不是因為它所用的語言不好(因為它也不差)。緩慢是由其他因素造成的。
我們想解決的就是這些因素:構建現代服務器軟件的復雜性:控制依賴性、與人員不斷變化的大型團隊一起編程、可維護性、高效測試、多核CPU和網絡的有效利用等等。
簡而言之,Go不僅僅是一種編程語言。當然,它是一種編程語言,這是它的定義。但它的目的是幫助提供一種更好的方式來開發高質量的軟件,至少與14多年前的我們的環境相比。
時至今日,這仍然是它的宗旨。Go是一個使構建生產軟件更容易、更高效的項目。
幾周前,當我開始準備這次演講時,我只有一個題目,除此之外別無其他。為了激發我的思路,我在Mastodon上向人們征求意見。不少人給予了回復。我注意到了一種趨勢:人們認為我們做錯的事情都在語言本身,而我們做對的事情都在語言周邊,比如gofmt、部署和測試等。事實上,我覺得這令人鼓舞。我們試圖做的事情似乎已經產生了效果。
但值得承認的是,我們在早期并沒有明確真正的目標。我們可能覺得這些目標是不言自明的。為了彌補這一缺陷,我在2013年的SPLASH會議上發表了一場題為《谷歌的Go語言:面向軟件工程的語言設計[10]》的演講。
Go at Google
那場演講和相關的博客文章可能是對Go語言為何而生的最好詮釋。
今天的演講是SPLASH演講的后續,回顧了我們在構建語言之后所學到的經驗教訓,并且可以更廣泛地應用于更大的圖景。
那么......來談談一些教訓。
首先,當然,我們有:
The Gopher
以Go Gopher吉祥物開始可能看起來是一個奇怪的起點,但Go gopher是Go成功的最早因素之一。在發布Go之前,我們就知道我們想要一個吉祥物來裝飾周邊商品——每個項目都需要周邊商品——Renee French主動提出為我們制作一個這樣的吉祥物。在這一點上,我們做得非常正確。
下面最早的Gopher毛絨玩具的圖片:
The Gopher
這是Gopher的照片,它的第一個原型不太成功。
Gopher和它進化程度較低的祖先
Gopher是一個吉祥物,它也是榮譽徽章,甚至是世界各地Go程序員的身份標志。此時此刻,你正在參加一個名為GopherCon的會議,這是眾多GopherCon會議中的一個。擁有一個從第一天就準備好分享信息的容易識別、有趣的生物,對Go的成長至關重要。它天真又聰明——它可以構建任何東西!
它為社區參與該項目奠定了基調,這是卓越的技術與真正的樂趣相結合的基調。最重要的是,Gopher是社區的一面旗幟,一面團結起來的旗幟,尤其是在早期,當Go還是編程界的新貴時。
這是幾年前Gopher參加巴黎會議的照片,看看他們多興奮!
盡管如此,在知識共享署名許可(Creative Commons Attribution license)下發布Gopher的設計也許不是最好的選擇。一方面,它鼓勵人們以有趣的方式重新組合他,這反過來又有助于培養社區精神。
Gopher model sheet
Renee創建了一個“模型表”來幫助藝術家在保持其精神原貌的同時進行藝術創作。
一些藝術家利用這些特征制作了自己版本的Gopher并獲得了樂趣;Renee和我最喜歡的版本是日本設計師@tottie的和游戲程序員@tenntennen的:
@tottie的Gopher
@tenntennen 的gopher
但許可證的“歸屬”部分常常會導致令人沮喪的爭論,或者導致Renee的創作不屬于她,也不符合原作的精神。而且,說實話,這種歸屬往往只是不情愿地得到尊重,或者根本沒有得到尊重。例如,我懷疑@tenntennen是否因他的Gopher插圖被使用而獲得補償或是得到承認。
gophervans.com: Boo!
因此,如果讓我們重來一次,我們會認真思考確保吉祥物忠于其理想的最佳方法。維護吉祥物是一件很難的事,而且解決方案仍然難以捉摸。
但更多的是技術性的事情。
做的對的事情
這里有一份我認為我們在客觀上做對了的事情的清單,特別是在回顧的時候。并不是每一個編程語言項目都做了這些事情,但清單中的每一件對Go的最終成功都至關重要。我會試著言簡意賅,因為這些話題都已為人所熟知。
1. 語言規范(Specification)
我們從正式的語言規范開始。這不僅可以在編寫編譯器時鎖定行為,還可以使多個編譯器實現共存并就該行為達成一致。編譯器本身并不是一個規范。你測試編譯器的依據是什么?
Web上的Go語言規范
哦,順便說一句,該規范的初稿是在這里編寫的,位于悉尼達令港一棟建筑的18層。我們正在Go的家鄉慶祝Go的生日。
2. 多種實現
Go有多個編譯器實現,它們都實現相同的語言規范。有了規范就可以更容易地實現這一點。
有一天,伊恩·泰勒(Ian Taylor)發郵件通知我們,在閱讀了我們的語言規范草案后,他自己編寫了一個編譯器,這讓我們感到驚訝!
Subject: A gcc frontend for Go
From: Ian Lance Taylor
Date: Sat, Jun 7, 2008 at 7:06 PM
To: Robert Griesemer, Rob Pike, Ken Thompson
One of my office-mates pointed me at http://.../go_lang.html . It
seems like an interesting language, and I threw together a gcc
frontend for it. It's missing a lot of features, of course, but it
does compile the prime sieve code on the web page.
這的確令人興奮,但更多的編譯器實現也隨之而來了,所有這些都因正式規范的存在而成為可能。
很多編譯器
擁有多個編譯器幫助我們改進了語言并完善了規范,并為那些不太喜歡我們類似Plan-9的業務方式的其他人提供了替代環境。稍后會詳細介紹。如今有很多兼容的實現,這很棒!
3. 可移植性
我們使Go應用的交叉編譯變得輕而易舉,程序員可以在他們喜歡的任何平臺上工作,并交付到任何需要的平臺。使用Go可能比使用任何其他語言更容易達成這一點。很容易將編譯器視為運行它的機器的本地編譯器,但沒有理由這么認為。打破這個假設具有重要意義,這對許多開發者來說都是新鮮事。
可移植性
4. 兼容性
我們努力使語言達到1.0版本的標準,然后通過兼容性保證將其固定下來,這對Go的采用產生了非常明顯的影響!我不理解為什么大多數其他項目一直在抵制這樣做。是的,保持強大兼容性的確需要付出成本,但它可以阻止功能特性停滯,而在這個幾乎沒有其他東西保持穩定的世界里,不必擔心新版本的Go會破壞你的項目,這足以令人感到欣喜!
Go兼容性承諾
5. 標準庫
盡管它的增長在某種程度上是偶然的,因為在一開始沒有其他地方可以安裝Go代碼,但擁有一個堅實、制作精良的標準庫,其中包含編寫21世紀服務器代碼所需的大部分內容,這是一個重大資產。在我們積累了足夠的經驗來理解還應該提供什么之前,它使整個社區都使用相同的工具包。這非常有效,并有助于防止出現不同版本的庫,從而幫助統一社區。
標準庫
6. 工具
我們確保該語言易于解析,從而支持工具構建。起初我們認為Go需要一個IDE,但易于構建工具意味著,隨著時間的推移,IDE將會出現在Go上。他們和gopls一起做到了,而且他們非常棒。
我們還為編譯器提供了一套輔助工具,例如自動化測試、覆蓋率和代碼審查(code vetting)。當然還有go命令,它集成了整個構建過程,也是許多項目構建和維護其Go代碼所需的一切。
此外,Go獲得了快速構建的聲譽,這也沒有什么壞處。
7. Gofmt
我將gofmt作為一個單獨的項目從工具中拿出來,因為它是一個不僅在Go上而且在整個編程社區上留下了印記的工具。在Robert編寫gofmt之前(順便說一句,他從一開始就堅持這樣做),自動格式化程序的質量不高,因此大多未被使用。
gofmt諺語
gofmt的成功表明了代碼自動格式化可以做得很好,今天幾乎每種值得使用的編程語言都有一個標準格式化程序。我們不再為空格和換行符爭論,這節省了大量時間了,這也讓那些花在定義標準格式和編寫這段相當困難的代碼實現格式自動化上的時間顯得超值。
此外,gofmt還使無數其他工具成為可能,例如簡化器、分析器甚至是代碼覆蓋率工具。因為gofmt的內容成為了任何人都可以使用的庫,所以你可以解析程序、編輯AST,然后打印完美的字節輸出,供人類和機器使用。
謝謝,羅伯特。
不過,恭喜你就夠了。接下來,我們來談談一些更有爭議的話題。
并發性
并發有爭議嗎?嗯,在我2002年加入谷歌的那年肯定有。John Ousterhout曾說過:線程很糟糕。許多人都同意他的觀點,因為線程似乎非常難以使用。
John Ousterhout不喜歡線程
谷歌的軟件幾乎總是避免使用它們,可以說是徹底禁止使用,而制定這一禁令的工程師引用了Ousterhout的言論。這讓我很困擾。自20世紀70年代以來,我一直在做類似的并發事情,有時候甚至沒有意識到,在我看來這很強大。但經過反思,很明顯Ousterhout犯了兩個錯誤。首先,他的結論超出了他有興趣使用線程的領域,其次,他主要是在抱怨使用笨拙的低級包如pthread之類的線程,而不是抱怨這一基本思想。
像這樣混淆解決方案和問題是世界各地工程師常犯的錯誤。有時,提出的解決方案比它解決的問題更難,并且很難看到有更簡單的路徑。但我離題了。
根據經驗,我知道有更好的方法來使用線程,或者無論我們選擇怎么稱呼它們,我甚至在Go語言出現之前就曾就此發表過演講。
Newsqueak中的并發
但我并不孤單,其他許多語言、論文甚至書籍都表明,并發編程可以做得很好,不僅我知道這一點。它只是還沒有在主流中流行起來,Go的誕生部分地就是為了解決這個問題。在那次臭名昭著的45分鐘構建中,我試圖向一個非線程二進制文件添加一個線程,這非常困難,因為我們使用了錯誤的工具。
回顧過去,我認為可以公平地說,Go在讓編程界相信并發是一種強大工具方面發揮了重要作用,特別是在多核網絡世界中,它可以比pthread做得更好。如今,大多數主流語言都對并發提供了很好地支持。
Google 3.0
另外,Go的并發版本在導致它出現的語言線中有些新穎,因為它使goroutine變得平淡無奇。沒有協程,沒有任務,沒有線程,沒有名稱,只有goroutine。我們發明了“goroutine”這個詞,因為沒有適合的現有術語。時至今日,我仍然希望Unix的拼寫命令可以學會它。
順便說一句,因為我經常被問到,讓我花一分鐘時間談談async/await。看到async/await模型及其相關風格成為許多語言選擇支持并發的方式,我有點難過,但它肯定是對pthreads的巨大改進。
與goroutine、channel和select相比,async/await對語言實現者來說更容易也更小,可以更容易地內建或后移植到現有平臺中。但它將一些復雜性推回給了程序員,通常會導致Bob Nystrom所著名的“彩色函數[11]”。
你的函數是什么顏色的
我認為Go表明了CSP這種不同但更古老的模型可以完美地嵌入到過程化語言中,沒有這種復雜性。我甚至看到它幾次作為庫實現。但它的實現,如果做得好,需要顯著的運行時復雜性,我可以理解為什么一些人更傾向于不在他們的系統中內置它。不管你提供什么并發模型,重要的是只提供一次,因為一個環境提供多個并發實現可能會很麻煩。Go當然通過把它放在語言中而不是庫中解決了這個問題。
關于這些問題可能要講整場演講,但目前就這些吧。
并發的另一個價值在于,它使Go看起來像是全新的東西。如我所說,一些其他語言在之前已經支持了它,但它們從未進入主流,而Go對并發的支持是吸引初學者采用的一個主要因素,它吸引了以前沒有使用過并發但對其可能性感興趣的程序員。
這就是我們犯下兩個大錯誤的地方。
圖片
耳語的Gopher(Cooperating Sequential Processes)
首先,并發很有趣,我們很高興擁有它,但我們設想的使用案例大多是服務器相關的,意在在net/http等關鍵庫中完成,而不是在每個程序的所有地方完成。當許多程序員使用它時,他們努力研究它如何真正幫助他們。我們應該一開始就解釋清楚,語言中的并發支持真正帶到桌面的是更簡單的服務器軟件。這個問題空間對許多人很重要,但并非所有嘗試Go的人都是如此,這點指導不足是我們的責任。
相關的第二點是,我們用了太長時間來澄清并行和并發之間的區別——支持在多核機器上并行執行多個計算,以及一種組織代碼的方式,以便很好地執行并行計算。
無數程序員試圖通過使用goroutine來并行化他們的代碼以使其更快,但經常對結果中的速度降低感到困惑。僅當基礎問題本質上是并行的時候,例如服務HTTP請求,并發代碼才會通過并行化而變快。我們在解釋這一點上做得很糟糕,結果讓許多程序員感到困惑,可能還趕走了一些人。
為了解決這個問題,我在2012年Waza上給Heroku的開發者大會做了一個題為“并發不是并行[12]”的演講。這是一次很有趣的演講,但它應該更早發生。
對此表示歉意。但好處仍然存在:Go幫助普及了并發性作為構建服務器軟件的一種方式。
接口
很明顯,接口與并發都是Go中與眾不同的思想。它們是Go對面向對象設計的答案,采用最初關注行為的風格,盡管新來者一直在努力使結構體承擔這一角色。
使接口動態化,無需提前宣布哪些類型實現了它們,這困擾了一些早期評論者,并且仍然惱火一小部分人,但它對Go培育的編程風格很重要。大部分標準庫都是建立在它們的基礎之上的,而更廣泛的主題如測試和管理依賴也高度依賴于它們慷慨的“歡迎所有人”的天性。
我覺得接口是Go中設計最好的部分之一。
除了一些早期關于接口定義中是否應該包括數據的討論之外,它們在討論的第一天就已經成形。
GIF 解碼器:Go接口的練習(Rob Pike和Nigel Tao 2011)
在這個問題上還有一個故事要講。
在Robert和我的辦公室里那著名的第一天,我們討論了關于多態性應該怎么處理的問題。Ken和我從C語言中知道qsort可以作為一個困難的測試用例,所以我們三個人開始討論用我們這種初具雛形的語言如何實現一個類型安全的排序例程(routine)。
Robert和我幾乎同時產生了同樣的想法:在類型上使用方法來提供排序所需的操作。這個概念很快發展成了一個想法,即值類型擁有作為方法定義的行為,一組方法可以提供函數可以操作的接口。Go的接口幾乎立即就出現了。
sort.Interface
有一點沒人經常提到:Go的sort函數是作為一個在接口上操作的函數實現的。這與大多數人熟悉的面向對象編程風格不同,但這是一個非常強大的想法。
這個想法對我們來說非常激動人心,它可能成為一個基礎的編程構造,這令我們陶醉。當Russ Cox加入時,他很快指出了I/O如何完美地融入這個想法,標準庫的發展非常迅速,在很大程度上依賴于三個著名的接口:空接口(interface{})、Writer和Reader,每個接口平均包含兩個第三個方法。那些微小的方法對Go來說是慣用法,無處不在。
接口的工作方式不僅成為Go的一個顯著特性,它們也成為我們思考庫、泛型和組合的方式。這是讓人興奮的事情。
但我們在這個問題上停止討論可能是一個錯誤。
你看,我們之所以走上這條路,至少在一定程度上是因為我們看到泛型編程太容易鼓勵一種傾向于在算法之前首先關注類型的思考方式。過早抽象而不是有機設計。容器而不是函數。
我們在語言中正確定義了通用容器——map,切片,數組,channel——而不給程序員訪問它們所包含的泛型。這可以說是一個錯誤。我們相信,我認為仍然正確的是,大多數簡單的編程任務可以很好地由這些類型來處理。但有一些不能,語言提供的和用戶可以控制的之間的障礙肯定困擾了一些人。
簡而言之,盡管我不會改變接口的任何工作方式,但它們以需要十多年時間才能糾正的方式影響了我們的思維。Ian Taylor從一開始就推動我們面對這個問題,但在接口作為Go編程基石的情況下,這是相當困難的。
評論者經常抱怨我們應該使用泛型,因為它們“很簡單”,在某些語言中可能確實如此,但接口的存在意味著任何新的多態形式都必須考慮到它們。找到一種可以與語言的其余部分很好地協同工作的前進方法需要多次嘗試,幾次中止的實現,以及許多小時、天數和周數的討論。最終,在Phil Wadler的帶領下,我們召集了一些類型理論家來提供幫助。即使在語言中有了可靠的泛型模型,作為方法集存在的接口也仍然存在一些遺留問題。
泛型版sort
如你所知,最終的答案是設計一個可以吸收更多多態形式的接口泛化,從“方法集合”過渡到“類型集合”。這是一個微妙但深刻的舉措,大多數社區似乎都可以接受,盡管我懷疑抱怨聲永遠不會停止。
有時候要花很多年的時間來弄清楚一些事情,或者甚至弄清楚你并不能完全弄明白它。但你還是要繼續前進。
順便說一句,我希望我們有一個比“泛型”更好的術語,它起源于表示一種不同的數據結構中心多態風格。“參數多態”是Go提供的該功能的正確術語,這是一個準確的術語,但它難聽。于是我們依然說“泛型”,盡管它不太恰當。
編譯器
困擾編程語言社區的一件事是,早期的Go編譯器是用C語言編寫的。在他們看來,正確的方式是使用LLVM或類似的工具包,或者用Go語言本身編寫編譯器,這稱為自舉。我們沒有做這兩者中的任何一種,原因有幾個。
首先,自舉一種新語言要求至少其編譯器的第一步必須用現有語言完成。對我們來說,C語言是顯而易見的選擇,因為Ken已經編寫了C編譯器,并且其內部結構可以很好地作為Go編譯器的基礎。此外,用自己的語言編寫編譯器,同時開發該語言,往往會產生一種適合編寫編譯器的語言,但這不是我們想要的語言。
早期的編譯器工作良好,它可以很好地引導語言。但從某種意義上說,它有點奇怪,實際上它是一個Plan 9風格的編譯器,使用舊的編譯器編寫思想,而不是新的思想,如靜態單一賦值(SSA)[13]。生成的代碼平庸,內部不太漂亮。但它是務實高效的,編譯器代碼本身體積適中,對我們來說也很熟悉,這使得我們在嘗試新想法時可以快速進行更改。一個關鍵步驟是添加自動增長的分段堆棧。這很容易添加到我們的編譯器中,但是如果我們使用像LLVM這樣的工具包,考慮到ABI和垃圾收集器支持所需的更改,將這種更改集成到完整的編譯器套件中是不可行的。
另一個工作良好的區域是交叉編譯,這直接來自原始Plan 9編譯器套件的工作方式。
按照我們的方式行事,無論多么非正統,都有助于我們快速前進。有些人對這一選擇感到冒犯,但這對當時的我們來說是正確的選擇。
對于Go 1.5版本,Russ Cox編寫了一個工具,可以半自動將編譯器從C轉換為Go。到那時,語言已經完成,編譯器導向的語言設計的擔憂也就無關緊要了。有一些關于這個過程的在線演講值得一看。我在2016年的GopherCon上做了一個關于匯編器的演講,這在我畢生追求可移植性的過程中是一個高點。
Go匯編器設計(GopherCon 2016)
我們從C開始做了正確的事情,但最終將編譯器翻譯為Go,使我們能夠將Go所具有的所有優勢帶到其開發中,包括測試、工具、自動重寫、性能分析等。當前的編譯器比原始編譯器干凈得多,并且可以生成更好的代碼。但是,當然,這就是自舉的工作原理。
請記住,我們的目標不僅僅是一種語言,而是更多。
我們不尋常的做法絕不是對LLVM或語言社區中任何人的侮辱。我們只是使用了最適合我們任務的工具。當然,今天有一個LLVM托管的Go編譯器,以及許多其他應該有的編譯器。
項目管理
我們從一開始就知道,要成功,Go必須是一個開源項目。但我們也知道,在弄清楚關鍵的思想和有一個工作的實現之前,私下開發會更高效。頭兩年對澄清我們在試圖實現什么,而不受干擾,是必不可少的。
向開源的轉變是一個巨大的改變,也很具教育意義。來自社區的投入是壓倒性的。與社區的接觸花費了大量的時間和精力,尤其是對Ian,不知怎么他找到時間來回答任何人提出的每一個問題。但它也帶來了更多。我仍然驚嘆在Alex Brainman的指導下,社區完全獨立完成的Windows移植的速度。那很神奇。
我們花了很長時間來理解轉向開源項目的影響,以及如何管理它。
特別是,公平地說,我們花了太長時間來理解與社區合作的最佳方式。本次演講的一個主題是我們的溝通不足——即使我們認為我們正在進行良好溝通——由于誤解和不匹配的期望,大量時間被浪費了。本可以做得更好。
但是,隨著時間的推移,我們說服了社區中的至少那一部分和我們在一起的人,我們的一些想法,雖然與常見的開源方式不同,但具有價值。最重要的是我們堅持通過強制代碼審查和對細節的窮盡關注來維護高質量代碼。
Mission Control (drawing by Renee French)
一些項目的工作方式不同,它們快速接受代碼,然后在提交后進行清理。Go項目則相反,力圖將質量放在第一位。我相信這是更有效的方式,但它將更多的工作推回社區,如果他們不理解其價值,他們就不會感到應有的歡迎。在這方面還有很多東西要學習,但我相信現在的情況已經好多了。
順便說一句,有一個歷史細節不是廣泛為人知的。該項目使用過4個不同的內容管理系統:SVN、Perforce、Mercurial和Git。Russ Cox做了一份艱巨的工作,保留了所有歷史,所以即使今天,Git倉庫也包含了在SVN中做出的最早的更改。我們都認為保留歷史很有價值,我要感謝他做了這項艱苦的工作。
還有一點。人們經常認為谷歌會告訴Go團隊該做什么。這絕對不是真的。谷歌對Go的支持非常慷慨,但它不制定議程。社區的投入要大得多。谷歌內部有一個巨大的Go代碼庫,團隊用它來測試和驗證版本,但這是通過從公共倉庫導入谷歌完成的,而不是反過來。簡而言之,核心Go團隊由谷歌支付薪水,但他們是獨立的。
包管理
Go的包管理開發過程做得并不好。我相信,語言本身的包設計非常出色,并且在我們討論的第一年左右的時間里消耗了大量的時間。如果你感興趣的話,我之前提到的SPLASH演講詳細解釋了它為什么會這樣工作。
一個關鍵點是使用純字符串來指定導入語句中的路徑,從而提供了我們正確認為很重要的靈活性。但從只有一個“標準庫”到從網絡導入代碼的轉變是坎坷的。
修復云(Renee French 繪制)
有兩個問題。
首先,我們這些Go核心團隊的成員很早就熟悉Google的工作方式,包括它的monorepo(單一代碼倉庫)和每個人都在負責構建。但是我們沒有足夠的經驗來使用具有大量包版本的包管理器以及嘗試解決依賴關系圖的非常困難的問題。直到今天,很少有人真正理解技術的復雜性,但這并不能成為我們未能從一開始就解決這些問題的借口。這尤其令人尷尬,因為我曾是一個失敗項目的技術負責人,為谷歌的內部構建做類似的事情,我應該意識到我們面臨的是什么。
deps.dev
我在deps.dev上的工作是一種懺悔。
其次,讓社區參與幫助解決依賴管理問題的初衷是好的,但當最終設計出來時,即使有大量的文檔和有關理論的文章,社區中的許多人仍然感到受到了輕視。
pkg.go.dev
這次失敗給團隊上了一課,讓他們知道如何真正與社區互動,并且自此取得了很大的進步。
不過,現在事情已經解決了,新的設計在技術上非常出色,并且似乎對大多數用戶來說效果很好。只是時間太長,而且道路崎嶇不平。
文檔和示例
我們事先沒有得到的另一件事是文檔。我們寫了很多文檔,并認為我們做得很好,但很快就發現社區想要的文檔級別與我們的預期不同。
修理圖靈機的Gopher(Renee French 繪圖)
關鍵缺失的一部分是最簡單函數的示例。我們曾以為只需說明某個東西的功能就足夠了,但我們花費了太長時間才接受到展示如何使用它的價值更大。
可執行的例子
不過,我們已經吸取了教訓。現在文檔中有很多示例,大部分是由開源貢獻者提供的。我們很早就做的一件事就是讓它們在網絡上可執行。我在2012年的Google I/O大會上做了一次演講,展示了并發的實際應用,Andrew Gerrand 編寫了一段可愛的Web goo,使得直接從瀏覽器運行代碼片段成為可能。我懷疑這是第一次這樣做,但Go是一種編譯語言,很多觀眾以前從未見過這個技巧。然后該技術被部署到博客和在線包文檔中。
Go playground
也許更重要的是我們對Go Playground的支持,這是一個免費的開放沙箱,供人們嘗試,甚至開發代碼。
結論
我們已經走了很長一段路。
回顧過去,很明顯很多事情都做得對,并且它們都幫助Go取得了成功。但還有很多事情可以做得更好,重要的是要承認這些問題并從中學習。對于任何托管重要開源項目的人來說,雙方都有教訓。
我希望我對這些教訓及其原因的歷史回顧會有所幫助,也許可以作為對那些反對我們正在做的事情和我們如何做的人的一種道歉/解釋。
GopherConAU 2023 吉祥物,作者:Renee French
但在推出 14 年后,我們終于來了。公平地說,總的來說這是一個非常好的地方。
很大程度上是因為通過設計和開發Go作為一種編寫軟件的方式(而不僅僅是作為一種編程語言)做出的決定,我們已經到達了一個新的地方。
我們到達這里的部分原因包括:
- 一個強大的標準庫,可實現服務器代碼所需的大部分基礎知識
- 并發作為該語言的“一等公民”
- 基于組合而不是繼承的方法
- 澄清依賴管理的打包模型
- 集成的快速構建和測試工具
- 嚴格一致的代碼格式
- 注重可讀性而非聰明性
- 兼容性保證
最重要的是,得益于令人難以置信的樂于助人且多元化的Gophers社區的支持。
多元化的社區(@tenntennen 繪圖)
也許這些問題最有趣的結果是,無論是誰編寫的Go代碼的外觀和工作原理都是一樣的,基本上沒有使用該語言的不同子集的派系,并且保證隨著時間的推移代碼可繼續編譯和運行。對于主要編程語言來說,這可能是第一次。
我們絕對做對了。