Go在谷歌:以软件工程为目的的语言设计
From: http://www.oschina.net/translate/go-at-google-language-design-in-the-service-of-software-engineering
?
1. 摘要(本文是根據(jù)Rob Pike于2012年10月25日在Tucson, Arizona舉行的SPLASH 2012大會(huì)上所做的主題演講進(jìn)行修改后所撰寫(xiě)的。) 針對(duì)我們?cè)贕oogle公司內(nèi)開(kāi)發(fā)軟件基礎(chǔ)設(shè)施時(shí)遇到的一些問(wèn)題,我們于2007年末構(gòu)思出Go編程語(yǔ)言。當(dāng)今的計(jì)算領(lǐng)域同創(chuàng)建如今所使用的編程語(yǔ)言(使用最多的有C++、Java和Python)時(shí)的環(huán)境幾乎沒(méi)什么關(guān)系了。由多核處理器、系統(tǒng)的網(wǎng)絡(luò)化、大規(guī)模計(jì)算機(jī)集群和Web編程模型帶來(lái)的編程問(wèn)題都是以迂回的方式而不是迎頭而上的方式解決的。此外,程序的規(guī)模也已發(fā)生了變化:現(xiàn)在的服務(wù)器程序由成百上千甚至成千上萬(wàn)的程序員共同編寫(xiě),源代碼也以數(shù)百萬(wàn)行計(jì),而且實(shí)際上還需要每天都進(jìn)行更新。更加雪上加霜的是,即使在大型編譯集群之上進(jìn)行一次build,所花的時(shí)間也已長(zhǎng)達(dá)數(shù)十分鐘甚至數(shù)小時(shí)。 之所以設(shè)計(jì)開(kāi)發(fā)Go,就是為了提高這種環(huán)境下的工作效率。Go語(yǔ)言設(shè)計(jì)時(shí)考慮的因素,除了大家較為了解的內(nèi)置并發(fā)和內(nèi)存垃圾自動(dòng)回收這些方面之外,還包括嚴(yán)格的依賴(lài)管理、對(duì)隨系統(tǒng)增大而在體系結(jié)構(gòu)方面發(fā)生變化的適應(yīng)性、跨組件邊界的健壯性(robustness)。 本文將詳細(xì)講解在構(gòu)造一門(mén)輕量級(jí)并讓人感覺(jué)愉悅的、高效的編譯型編程語(yǔ)言時(shí),這些問(wèn)題是如何得到解決的。講解過(guò)程中使用的例子都是來(lái)自Google公司中所遇到的現(xiàn)實(shí)問(wèn)題。 | |
| ? |
2. 簡(jiǎn)介Go語(yǔ)言開(kāi)發(fā)自Google,是一門(mén)支持并發(fā)編程和內(nèi)存垃圾回收的編譯型靜態(tài)類(lèi)型語(yǔ)言。它是一個(gè)開(kāi)源的項(xiàng)目:Google從公共的代碼庫(kù)中導(dǎo)入代碼而不是相反。 Go語(yǔ)言運(yùn)行效率高,具有較強(qiáng)的可伸縮性(scalable),而且使用它進(jìn)行工作時(shí)的效率也很高。有些程序員發(fā)現(xiàn)用它編程很有意思;還有一些程序員認(rèn)為它缺乏想象力甚至很煩人。在本文中我們將解釋為什么這兩種觀點(diǎn)并不相互矛盾。Go是為解決Google在軟件開(kāi)發(fā)中遇到的問(wèn)題而設(shè)計(jì)的,雖然因此而設(shè)計(jì)出的語(yǔ)言不會(huì)是一門(mén)在研究領(lǐng)域里具有突破性進(jìn)展的語(yǔ)言,但它卻是大型軟件項(xiàng)目中軟件工程方面的一個(gè)非常棒的工具。 | |
| ? |
3. Google公司中的Go語(yǔ)言為了幫助解決Google自己的問(wèn)題,Google設(shè)計(jì)了Go這門(mén)編程語(yǔ)言,可以說(shuō),Google有很大的問(wèn)題。 硬件的規(guī)模很大而且軟件的規(guī)模也很大。軟件的代碼行數(shù)以百萬(wàn)計(jì),服務(wù)器軟件絕大多數(shù)用的是C++,還有很多用的是Java,剩下的一部分還用到了Python。成千上萬(wàn)的工程師在這些代碼上工作,這些代碼位于由所有軟件組成的一棵樹(shù)上的“頭部”,所以每天這棵樹(shù)的各個(gè)層次都會(huì)發(fā)生大量的修改動(dòng)作。盡管使用了一個(gè)大型自主設(shè)計(jì)的分布式Build系統(tǒng)才讓這種規(guī)模的開(kāi)發(fā)變得可行,但這個(gè)規(guī)模還是太大 了。 當(dāng)然,所有這些軟件都是運(yùn)行在無(wú)數(shù)臺(tái)機(jī)器之上的,但這些無(wú)數(shù)臺(tái)的機(jī)器只是被看做數(shù)量并不多若干互相獨(dú)立而僅通過(guò)網(wǎng)絡(luò)互相連接的計(jì)算機(jī)集群。 簡(jiǎn)言之,Google公司的開(kāi)發(fā)規(guī)模很大,速度可能會(huì)比較慢,看上去往往也比較笨拙。但很有效果。 Go項(xiàng)目的目標(biāo)是要消除Google公司軟件開(kāi)發(fā)中的慢速和笨拙,從而讓開(kāi)發(fā)過(guò)程更加高效并且更加具有可伸縮性。該語(yǔ)言的設(shè)計(jì)者和使用者都是要為大型軟件系統(tǒng)編寫(xiě)、閱讀和調(diào)試以及維護(hù)代碼的人。 因此,Go語(yǔ)言的目的不是要在編程語(yǔ)言設(shè)計(jì)方面進(jìn)行科研;它要能為它的設(shè)計(jì)者以及設(shè)計(jì)者的同事們改善工作環(huán)境。Go語(yǔ)言考慮更多的是軟件工程而不是編程語(yǔ)言方面的科研。或者,換句話說(shuō),它是為軟件工程服務(wù)而進(jìn)行的語(yǔ)言設(shè)計(jì)。 但是,編程語(yǔ)言怎么會(huì)對(duì)軟件工程有所幫助呢?下文就是該問(wèn)題的答案。 |
4. 痛之所在當(dāng)Go剛推出來(lái)時(shí),有人認(rèn)為它缺乏某些大家公認(rèn)的現(xiàn)代編程語(yǔ)言中所特有的特性或方法論。缺了這些東西,Go語(yǔ)言怎么可能會(huì)有存在的價(jià)值?我們回答這個(gè)問(wèn)題的答案在于,Go的確具有一些特性,而這些特性可以解決困擾大規(guī)模軟件開(kāi)發(fā)的一些問(wèn)題。這些問(wèn)題包括:
一門(mén)語(yǔ)言每個(gè)單個(gè)的特性都解決不了這些問(wèn)題。這需要從軟件工程的大局觀,而在Go語(yǔ)言的設(shè)計(jì)中我們?cè)噲D致力于解決所有這些問(wèn)題。 舉個(gè)簡(jiǎn)單而獨(dú)立的例子,我們來(lái)看看程序結(jié)果的表示方式。有些評(píng)論者反對(duì)Go中使用象C一樣用花括號(hào)表示塊結(jié)構(gòu),他們更喜歡Python或Haskell風(fēng)格式,使用空格表示縮進(jìn)。可是,我們無(wú)數(shù)次地碰到過(guò)以下這種由語(yǔ)言交叉Build造成的Build和測(cè)試失敗:通過(guò)類(lèi)似SWIG調(diào)用的方式,將一段Python代碼嵌入到另外一種語(yǔ)言中,由于修改了這段代碼周?chē)囊恍┐a的縮進(jìn)格式,從而導(dǎo)致Python代碼也出乎意料地出問(wèn)題了并且還非常難以覺(jué)察。?因此,我們的觀點(diǎn)是,雖然空格縮進(jìn)對(duì)于小規(guī)模的程序來(lái)說(shuō)非常適用,但對(duì)大點(diǎn)的程序可不盡然,而且程序規(guī)模越大、代碼庫(kù)中的代碼語(yǔ)言種類(lèi)越多,空格縮進(jìn)造成的問(wèn)題就會(huì)越多。為了安全可靠,舍棄這點(diǎn)便利還是更好一點(diǎn),因此Go采用了花括號(hào)表示的語(yǔ)句塊。 |
5.C和C++中的依賴(lài)在處理包依賴(lài)(package dependency)時(shí)會(huì)出現(xiàn)一些伸縮性以及其它方面的問(wèn)題,這些問(wèn)題可以更加實(shí)質(zhì)性的說(shuō)明上個(gè)小結(jié)中提出的問(wèn)題。讓我們先來(lái)回顧一下C和C++是如何處理包依賴(lài)的。 ANSI C第一次進(jìn)行標(biāo)準(zhǔn)化是在1989年,它提倡要在標(biāo)準(zhǔn)的頭文件中使用#ifndef這樣的"防護(hù)措施"。 這個(gè)觀點(diǎn)現(xiàn)已廣泛采用,就是要求每個(gè)頭文件都要用一個(gè)條件編譯語(yǔ)句(clause)括起來(lái),這樣就可以將該頭文件包含多次而不會(huì)導(dǎo)致編譯錯(cuò)誤。比如,Unix中的頭文件<sys/stat.h>看上去大致是這樣的: ?
此舉的目的是讓C的預(yù)處理器在第二次以及以后讀到該文件時(shí)要完全忽略該頭文件。符號(hào)_SYS_STAT_H_在文件第一次讀到時(shí)進(jìn)行定義,可以“防止”后繼的調(diào)用。 這么設(shè)計(jì)有一些好處,最重要的是可以讓每個(gè)頭文件能夠安全地include它所有的依賴(lài),即時(shí)其它的頭文件也有同樣的include語(yǔ)句也不會(huì)出問(wèn)題。?如果遵循此規(guī)則,就可以通過(guò)對(duì)所有的#include語(yǔ)句按字母順序進(jìn)行排序,讓代碼看上去更整潔。 但是,這種設(shè)計(jì)的可伸縮性非常差。 |
| 在1984年,有人發(fā)現(xiàn)在編譯Unix中ps命令的源程序ps.c時(shí),在整個(gè)的預(yù)處理過(guò)程中,它包含了<sys/stat.h>這個(gè)頭文件37次之多。盡管在這么多次的包含中有36次它的文件的內(nèi)容都不會(huì)被包含進(jìn)來(lái),但絕大多數(shù)C編譯器實(shí)現(xiàn)都會(huì)把"打開(kāi)文件并讀取文件內(nèi)容然后進(jìn)行字符串掃描"這串動(dòng)作做37遍。這么做可真不聰明,實(shí)際上,C語(yǔ)言的預(yù)處理器要處理的宏具有如此復(fù)雜的語(yǔ)義,其勢(shì)必導(dǎo)致這種行為。 對(duì)軟件產(chǎn)生的效果就是在C程序中不斷的堆積#include語(yǔ)句。多加一些#include語(yǔ)句并不會(huì)導(dǎo)致程序出問(wèn)題,而且想判斷出其中哪些是再也不需要了的也很困難。刪除一條#include語(yǔ)句然后再進(jìn)行編譯也不太足以判斷出來(lái),因?yàn)檫€可能有另外一條#include所包含的文件中本身還包含了你剛剛刪除的那條#include語(yǔ)句。 從技術(shù)角度講,事情并不一定非得弄成這樣。在意識(shí)到使用#ifndef這種防護(hù)措施所帶來(lái)的長(zhǎng)期問(wèn)題之后,Plan 9的library的設(shè)計(jì)者采取了一種不同的、非ANSI標(biāo)準(zhǔn)的方法。Plan 9禁止在頭文件中使用#include語(yǔ)句,并要求將所有的#include語(yǔ)句放到頂層的C文件中。 當(dāng)然,這么做需要一些訓(xùn)練 —— 程序員需要一次列出所有需要的依賴(lài),還要以正確的順序排列 —— 但是文檔可以幫忙而且實(shí)踐中效果也非常好。這么做的結(jié)果是,一個(gè)C源程序文件無(wú)論需要多少依賴(lài),在對(duì)它進(jìn)行編譯時(shí),每個(gè)#include文件只會(huì)被讀一次。當(dāng)然,這樣一來(lái),對(duì)于任何#include語(yǔ)句都可以通過(guò)先拿掉然后在進(jìn)行編譯的方式判斷出這條#include語(yǔ)句到底有無(wú)include的必要:當(dāng)且僅當(dāng)不需要該依賴(lài)時(shí),拿掉#include后的源程序才能仍然可以通過(guò)編譯。 |
| Plan 9的這種方式產(chǎn)生的一個(gè)最重要的結(jié)果是編譯速度比以前快了很多:采用這種方式后編譯過(guò)程中所需的I/O量,同采用#ifndef的庫(kù)相比,顯著地減少了不少。 但在Plan 9之外,那種“防護(hù)”式的方式依然是C和C++編程實(shí)踐中大家廣為接受的方式。實(shí)際上,C++還惡化了該問(wèn)題,因?yàn)樗堰@種防護(hù)措施使用到了更細(xì)的粒度之上。按照慣例,C++程序通常采用每個(gè)類(lèi)或者一小組相關(guān)的類(lèi)擁有一個(gè)頭文件這種結(jié)構(gòu),這種分組方式要更小,比方說(shuō),同<stdio.h>相比要小。因而其依賴(lài)樹(shù)更加錯(cuò)綜復(fù)雜,它反映的不是對(duì)庫(kù)的依賴(lài)而是對(duì)完整類(lèi)型層次結(jié)構(gòu)的依賴(lài)。而且,C++的頭文件通常包含真正的代碼 —— 類(lèi)型、方法以及模板聲明 ——不像一般的C語(yǔ)言頭文件里面僅僅有一些簡(jiǎn)單的常量定義和函數(shù)簽名。這樣,C++就把更多的工作推給了編譯器,這些東西編譯起來(lái)要更難一些,而且每次編譯時(shí)編譯器都必須重復(fù)處理這些信息。當(dāng)要build一個(gè)比較大型的C++二進(jìn)制程序時(shí),編譯器可能需要成千上萬(wàn)次地處理頭文件<string>以了解字符串的表示方式。(根據(jù)當(dāng)時(shí)的記錄,大約在1984年,Tom Cargill說(shuō)道,在C++中使用C預(yù)處理器來(lái)處理依賴(lài)管理將是個(gè)長(zhǎng)期的不利因素,這個(gè)問(wèn)題應(yīng)該得到解決。) |
| 在Google,Build一個(gè)單個(gè)的C++二進(jìn)制文件就能夠數(shù)萬(wàn)次地打開(kāi)并讀取數(shù)百個(gè)頭文件中的每個(gè)頭文件。在2007年,Google的build工程師們編譯了一次Google里一個(gè)比較主要的C++二進(jìn)制程序。該文件包含了兩千個(gè)文件,如果只是將這些文件串接到一起,總大型為4.2M。將#include完全擴(kuò)展完成后,就有8G的內(nèi)容丟給編譯器編譯,也就是說(shuō),C++源代碼中的每個(gè)自己都膨脹成到了2000字節(jié)。 還有一個(gè)數(shù)據(jù)是,在2003年Google的Build系統(tǒng)轉(zhuǎn)變了做法,在每個(gè)目錄中安排了一個(gè)Makefile,這樣可以讓依賴(lài)更加清晰明了并且也能好的進(jìn)行管理。一般的二進(jìn)制文件大小都減小了40%,就因?yàn)橛涗浟烁鼫?zhǔn)確的依賴(lài)關(guān)系。即使如此,C++(或者說(shuō)C引起的這個(gè)問(wèn)題)的特性使得自動(dòng)對(duì)依賴(lài)關(guān)系進(jìn)行驗(yàn)證無(wú)法得以實(shí)現(xiàn),直到今天我們?nèi)匀晃野l(fā)準(zhǔn)確掌握Google中大型的C++二進(jìn)制程序的依賴(lài)要求的具體情況。 |
| 由于這種失控的依賴(lài)關(guān)系以及程序的規(guī)模非常之大,所以在單個(gè)的計(jì)算機(jī)上build出Google的服務(wù)器二進(jìn)制程序就變得不太實(shí)際了,因此我們創(chuàng)建了一個(gè)大型分布式編譯系統(tǒng)。該系統(tǒng)非常復(fù)雜(這個(gè)Build系統(tǒng)本身也是個(gè)大型程序)還使用了大量機(jī)器以及大量緩存,藉此在Google進(jìn)行Build才算行得通了,盡管還是有些困難。 即時(shí)采用了分布式Build系統(tǒng),在Google進(jìn)行一次大規(guī)模的build仍需要花幾十分鐘的時(shí)間才能完成。前文提到的2007年那個(gè)二進(jìn)制程序使用上一版本的分布式build系統(tǒng)花了45分鐘進(jìn)行build。現(xiàn)在所花的時(shí)間是27分鐘,但是,這個(gè)程序的長(zhǎng)度以及它的依賴(lài)關(guān)系在此期間當(dāng)然也增加了。為了按比例增大build系統(tǒng)而在工程方面所付出的勞動(dòng)剛剛比軟件創(chuàng)建的增長(zhǎng)速度提前了一小步。 |
6. 走進(jìn) Go 語(yǔ)言當(dāng)編譯緩慢進(jìn)行時(shí),我們有充足的時(shí)間來(lái)思考。關(guān)于 Go 的起源有一個(gè)傳說(shuō),話說(shuō)正是一次長(zhǎng)達(dá)45分鐘的編譯過(guò)程中,Go 的設(shè)想出現(xiàn)了。人們深信,為類(lèi)似谷歌網(wǎng)絡(luò)服務(wù)這樣的大型程序編寫(xiě)一門(mén)新的語(yǔ)言是很有意義的,軟件工程師們認(rèn)為這將極大的改善谷歌程序員的生活質(zhì)量。 盡管現(xiàn)在的討論更專(zhuān)注于依賴(lài)關(guān)系,這里依然還有很多其他需要關(guān)注的問(wèn)題。這一門(mén)成功語(yǔ)言的主要因素是:
說(shuō)完了背景,現(xiàn)在讓我們從軟件工程的角度談一談 Go 語(yǔ)言的設(shè)計(jì)。 |
總結(jié)
以上是生活随笔為你收集整理的Go在谷歌:以软件工程为目的的语言设计的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ORACLE TDE 透明数据加密技术
- 下一篇: mybatis动态sql中的where标