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