這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
前言
如遇排版問題,請點擊 閱讀原文
我最近由於一些私活開始了對 Go 語言的探索,在這個過程中,我真的被 Go 語言的美妙震驚到了!
我發現,他在易用性(相比於動態類型和解釋性語言)、效能和安全(型別安全、記憶體安全)(相比於靜態類型與編譯型語言)之間取得了一個很好的平衡。
除此之外,還有兩個屬性讓它更加適合現代程式開發。由於篇幅略長,我們將在下面進行分別講解:
這兩個特殊屬性之一,便是在語言層面上優先支援並發。並發,在它設計上,允許你更充分地發揮 CPU 的效能,即使你的是單核 CPU,也能夠高效地使用。這也是你能在單台機器上擁有成千上萬 goroutines(go 協程)的原因。Channel(通道)和 goroutines 是基於 生產者與消費者模型
的核心發布系統。
另一個我真的很喜歡的屬性便是 interface(介面)。Go 語言的 interface 定義非常寬鬆,則允許你能夠為你的系統定義耦合或者非耦合的組件。這意味著,你的一部分代碼完全可以僅僅依賴於 interface 且並不需要關心是誰在使用或者怎麼使用這個 interface。你的控制器只需要滿足這個 interface 的依賴即可使用了(滿足這個 interface 中的所有函數)。這也讓我們有了一個非常整潔的代碼結構,便於以後的單元測試(只需要依賴注入即可)。現在,你的控制器只需要注入一個類比的依賴到該代碼的 interface,就能夠測試這段代碼的運行情況了。
牢記這些特性,我覺得 Go 語言是一個非常偉大的語言。尤其是在雲端服務系統(Web 服務器、CDN 伺服器、快取服務器等)、分布式系統、微服務等。因此,如果你是一個工程師或者正在尋找一門語言來學習,Go 值得你嘗試!
在這篇文章中,我會談到 Go 語言以下幾個方面的內容:
- Go 語言簡介
- 為什麼需要 Go 語言
- 目標使用者
- Go 語言的優勢
- Go 語言的不足
- 迎接 Go 語言 2 版本
- Go 的設計哲學
- 如何開始
- 誰在使用 Go
1. Go 語言簡介
Go 語言是一門開源的語言,由 Google 的 Robert Griesemer,Rob Pike 以及 Ken Thompson 建立。開源意味著任何人都能夠依照開源協定來打造 Go 語言,為其增加新功能,修複 Bug 等等。其源碼可以在 Github 上獲得 golang/go,關於如何參與貢獻的文檔在這裡。
2. 為什麼需要 Go 語言
該語言的作者們提到說:設計 Go 這門新語言的目的是為瞭解決 Google 在軟體開發中遇到的一些問題。他們同樣表示,設計 Go 是為了能在 C++ 之外有另一個選擇。
Rob Pike 表示,設計 Go 語言的意圖在於:
“Go’s purpose is therefore not to do research into programming language design; it is to improve the working environment for its designers and their coworkers. Go is more about software engineering than programming language research. Or to rephrase, it is about language design in the service of software engineering.”
Go 的設計意圖不在於對程式設計語言的設計做研究,而是為了改善它的設計者與同事之間的工作環境而已。Go 更多的是為了工程需要而不是專註於語言研究。換句話說,它的設計是為軟體工程服務的。
來自 Go at Google中描述的問題:
- 極慢的構建,有的時候會花掉一個多小時去構建項目;
- 不可控的依賴;
- 每個程式員的語言棧不同;
- 極差的代碼可讀性,難以理解的代碼、劣質的文檔等等;
- 重複的工作;
- 語言版本升級代價高;
- 版本分支混亂;
- 難以編寫自動化工具;
- 跨語言構建;
Go 想要成功,則必須要解決這些問題:
- Go 必須要是有條理的。以應對大的團隊協作以及繁多的依賴管理;
- Go 必須要是熟悉的。大致上和 C 差不多。Google 需要編程人員儘快地適應 Go 語言,這意味著,它的文法不能太過於激進;
- Go 必須要夠現代。它應該具有像
並發
這樣的特性,這樣才能夠最大限度發揮多核處理器的效能。它應當內建網路和 Web 服務器的庫,這樣他才能有助於現代化的軟體開發。
3. 目標使用者
Go 是一門系統程式設計語言。Go 非常適合在雲端服務器系統(Web Server、快取服務器等)、微服務、分布式系統(基於並發的支援)上應用。
4. Go 語言的優勢
- 靜態類型:Go 是靜態類型語言。這意味著,在編寫的過程中,你需要為你所有的變數以及函數的參數和其傳回值指定具體的類型。儘管這聽起來不太方便,但是這極大地避免了錯誤的發生,很多錯誤都能夠在編譯的時候被發現。隨著你團隊的增大,這個特性將會愈來愈重要,因為靜態類型能讓你的函數或者庫更易讀也更容易被理解;
- 編譯速度:Go 的代碼編譯真的很快,所以,你根本無需為編譯你的代碼而等待。事實上,當你使用
go run
這段代碼運行你程式的時候,你幾乎感受不到這個程式還要經過編譯!使用起來就像解釋型語言;
- 執行速度:根據不同的運行環境(OS:Linux/Windows/MacOS,CPU 類型:x86/x86-64/arm等),Go 語言的代碼最終是被編譯為了機器碼。所以,它運行得非常快;
- 可移植性:因為是直接編譯為的機器碼,我們得到的就是最終的二進位檔案,所以它的可移植性非常的好。例如,你可以將你電腦(假設是 Linux,x86-64)中編譯的二進位代碼直接複製到伺服器(假設也是 Linux,x86-64)上運行。
因為 Go 是靜態連結的,所謂靜態連結,就是 Go 在編譯的時候,會將所有用到的庫統一打包進最終的二進位檔案,並不會共用系統的任何運行庫。這就讓他在啟動並執行時候不再依賴當前作業系統庫(比如像平時在 Windows 上運行程式經常會出現各種庫檔案找不到的情況)。
這個特性在你部署大量伺服器時會產生巨大的作用。假設你資料中心有 100 台伺服器需要部署,你僅僅只需要用 scp
這個命令將二進位檔案自動拷貝到所有伺服器(二進位檔案是對應這些平台編譯的),直接運行就行了。你不用再關心它們啟動並執行是哪個版本的 Linux,無須檢查依賴,直接運行二進位程式即可,一切就都搞定了;
並發:Go 最優先地支援了並發,並發是 Go 語言主要的賣點之一。它的設計者根據 Tony Hoare 的論文 Communicating Sequential Processes 設計了 Go 的並行存取模型。
Go 的協程允許你在單台機器上擁有成千上萬的 goroutine
。一個 goroutine
是一個極其輕量的可執行線程,Go 能夠在系統線程之上調度這些協程運行,這意味著,大量的 goroutine
可以並發的在單個系統線程中運行。
這個方式有兩個好處:
- 一個
goroutine
在被建立的時候只有 4KB 的棧大小,這比起系統線程的 4MB 來說真的很小。這在你有成千上萬 goroutine
的時候變得至關重要。如果你想運 行成千個系統線程,那裡的 RAM 將會成為瓶頸。
- Go 可以遵循和 Java 一樣的模型,支援和系統線程一樣的上下文環境。但是,在不同系統線程上下文之間切換的開銷比在不同
goroutine
環境切換的開銷大多了。
至此,我在文中已經多次提到了並發
這個概念,我建議你去看一下 Rob Pike 的演講並發並不是並行 - YouTube。在編程中,並發是指一組互不相關的處理任務隊列,而並行則是同時執行的一組任務,這些任務有可能相關。除非你有一個多核的 CPU 或者有多個 CPU,你無法達到真正的並行,因為一個 CPU 在任何時間點只能執行一個任務。在單核環境中,在後台環境中,只有並發。系統通過調度處理隊列(通常是調度線程,每個進程都至少會有一個主線程),在每個 CPU 時間片中執行。所以,在任何時間點,你只可能有一個線程(進程)在處理器中執行。只是由於這些任務都切換得非常快(每秒能發產生千上萬次調度),所以感覺這些任務都是在同時執行的,但事實上他們真的只是排著隊在依次執行而已;
- Interface 介面:介面使系統之間的耦合變得更加寬鬆。在 Go 中,一個
interface
可以定義一組函數。就這麼簡單。任何實現了這組函數功能的類型就實現了這個 interface
,換句話說,你並不需要顯性地指明這個類型實現了這個介面。這些都由編譯器在編譯的過程中自動檢測的。
這意味著,你的某些代碼中只需要依賴於一個介面,而不用關心是誰實現了它,或者怎麼實現的。你的主函數或者控制器會提供一個依賴給這個介面(實現它裡面的所有方法)給這段代碼。這也讓你的整個項目結構更加清晰整潔,也更有利於單元測試(通過依賴注入)。現在你的測試函數只需要注入一個類比的介面實現,便可以檢測函數是否正確運行。
這對於解耦來說當然就是利器,還有一個好處就是,你能按照微服務的思考方式來構建你的軟體結構(即使你只有一台伺服器或者你剛開始著手做這個事情)。你可以將你軟體中的依賴想像成微服務,這些微服務都對應實現某個介面(按照它定義的)。這樣,其他服務或者控制器就只需要按照 interface
調用你的方法即可,並不需要關心這背後是如何?的;
- 記憶體回收:不像 C,在 Go 中,你不需要時刻記住釋放指標,也不用擔心錯誤的
dangling pointers
懸垂指標,記憶體回收行程會自動幫你完成這些工作;
- 沒有異常,手動管理 Errors:我喜歡這個功能,Go 並沒有像其他語言具有的標準異常處理邏輯。Go 強制要求開發人員自己處理基礎的錯誤,比如“不能開啟檔案”等,而不是通過
try{}catch(){}
來處理,這也強制開發人員去思考每個錯誤後應該怎樣去處理。
絕贊的工具:Go 語言最好的方面之一便是它的工具生態。它有以下的工具:
- Gofmt:它會自動格式化你的代碼檔案,按照標準格式。所以能夠實現地球上所有程式員開發出來的 Go 代碼都是同樣的風格。這對代碼可讀性的提升有至關重要的作用。
- Go run:這個代碼直接編譯並運行你的代碼。所以,即使 Go 是需要編譯才能啟動並執行語言,他也能讓你感覺這就像是在用解釋型語言一樣(它的編譯太快了)。
- Go get:這個工具會自動從 Github 下載庫,並拷貝到你的
GoPath
目錄下,你就直接能夠引用並運行了。
- Godoc:Godoc 能夠解釋你的原始碼(包含注釋),然後將它們輸出為 HTML 檔案或者簡單的 txt 檔案。通過 Godoc 的 Web 介面,你就可以看文檔所對應的那段代碼了。你可以直接點擊一個方法名,然後開啟這個方法對應的文檔。
你可以在這裡找到更多的工具。
絕佳的內建庫:Go 有大量的內建庫,以適應現代編程所需。比如下面這些:
- net/http:提供 HTTP 用戶端與服務端的實現
- database/sql:用作串連資料庫
- encoding/json:處理 json 內容
- html/templates:HTML 模版庫
- io/ioutil:提供 I/O 所需的一些方法實現
還有很多工具以及庫能夠在這裡找到。
5. Go 語言的不足
- 缺少泛型:泛型能讓我們在定義資料結構的時候不用事先傳入資料類型,在後面的使用中再傳入。比如,你寫了一個給列表中
int
資料排序的程式,一會兒,你又需要寫一個給 string
資料排序的函數。第一反應你會覺得,這兩個函數其實很多都是一樣的,但是你卻不能使用之前那個函數,因為它只能處理 int
列表,而不能處理 string
列表。這就會導致寫重複的代碼。因此,泛型允許你定義一個資料結構,裡面的資料類型可以稍後確定。所以,上面的例子你就可以這樣實現:先定義一個排序方法,資料的類型用 T
代替,然後你就能代入 int/string/任何
類型到同一個函數中去,對應每個類型都會有獨自的比較函數,編譯器在編譯的時候就能檢測對應的類型,然後使用該類型的比較函數進行比較即可。其實能夠通過聲明一個空的 interface(interface{}
)來在 Go 中實現一些泛型的結構。然而,這並不是理想的使用方式。
泛型這個特性也是最具爭議的話題。一些開發人員發誓要將其添加進 Go,但另一些則表示,不添加泛型是考慮到編譯速度和執行速度而做的權衡。
之前有說,Go 的作者們有意向在 Go 中實現一個泛型的機制。然而,這並不是泛型,泛型要能夠與其他所有特性協調工作才有可能被實現。我們可以等等看 Go 2 版本中會不會針對這個特性有解決方案。
- 缺少完整的依賴管理:Go 1 版本承諾了,它的所有 API 在 1 版本這個生命週期中都不會改變,這意味著,你的源碼能夠同時工作在 Go 1.5 或者 Go 1.9。在此之後,大部分的三方外掛程式都遵循了這個承諾。我們獲得三方庫主要是通過
go get
這個命令工具,因此,當你執行 go get github.com/vendor/library
的時候,你會更希望最新代碼的 API 並沒有發生改變。雖然大多數三方庫的開發人員都承諾不會改變 API,這在平時開發中倒是很不錯,但是對於產品來說,這並不是一個理想的方式。我們需要有一個理想的依賴版本控制功能,只有這樣,你才能簡單地引入指定版本的三方庫到你項目中。即使它們的 API 改變了,你也不用擔心,因為新的 API 改變肯定會是新的版本。你可以稍後去查看他哪些地方改變了,然後決定是否要更新到最新的版本(升級這意味著需要對之前的代碼做相應改動以適應最新的 API)。
Go 官方實現庫 dep 很理想地解決了這個問題,不過要等正式發布可能會到 Go 2 去了。
6. 迎接 Go 語言 2 版本
我非常喜歡 Go 語言的作者通過開源這種方式發布語言。如果你想在 Go 2 中添加新功能,你需要將你的請求寫成一個文檔:
- 描述你的使用情境或者問題
- 闡述為什麼你不能使用 Go 實現這個功能或解決這個問題
- 描述這個問題的影響(一些影響較小的問題可能會被排在最後來解決)
- 可選,針對上面的問題提出自己的解決方案
作者會查看你的文檔,並將其放到這裡。所有的討論都會通過公開的媒體展示出來,比如郵件清單或者 Github issue。
兩個亟待解決的問題,我認為就是泛型
和依賴管理
。依賴管理更像是一個發布工程或者工具問題。希望 dep(官方實現)能夠成為正式的解決方案。基於該語言的作者是抱著開放的態度,我很好奇等到泛型實現的時候,會對編譯速度與執行速度有什麼影響。
7. Go 的設計哲學
Rob Pike 的 PPT 簡單即複雜 中有一些思想讓我眼前一亮。
尤其是,下面這些:
- 基本上,其他語言僅僅只是想不斷的為其增加新特性。確實,幾乎所有的語言都是不斷的增加新功能,讓其變得臃腫,編譯器也越來越複雜,語言的使用手冊也是一樣。長此以往,所有的程式設計語言都會變得一樣,因為他們一直在增加自身沒有的功能。想想,JavaScript 增加物件導向的功能。Go 的作者刻意地控制著不讓太多功能增加到這門語言上。只有所有作者都一致同意並且真正能帶來價值的功能才會被添加進 Go(前提是 Go 能夠實現這個功能)。
- 如果當前所有解決方案空間是一個維度,那新功能則是與其正交的另一個維度。真正重要的是,你如何選取並組合這些不同的向量來解決你的問題。並且,這些向量與向量都很自然地工作在一起。意味著,所有的屬性都能夠非常融洽的結合。只有這樣,這些屬性向量才能夠支撐起整個解決方案空間。引入所有這些功能,並且要能夠讓他們協調地運行,這對於整個系統的實現來說,帶來更高的複雜度。但是這個語言將一切複雜的東西都做了抽象,只留給一個簡單的、易於理解的介面給使用者。因此,簡潔其實更是一種隱藏複雜的藝術。
- 可讀性的重要性常常被低估。可讀性其實是批判地、有爭議的,它是設計程式設計語言中最重要的因素之一,因為它和軟體的維護成本息息相關。太多的特性會降低語言的可讀性。
- 可讀性通常也意味著可靠性。如果一個語言很複雜,你必須要瞭解很多知識才能讀並用這個語言工作(調試甚至是修複程式)。這也意味著,你團隊中的新成員在能夠開始編碼前,需要花費更多的時間來學習、理解。
8. 如何開始
你可以下載並依照著安裝說明來在你的電腦上安裝 Go 編程環境。由於 Go 在中國真的很火爆,但是它的官網由於某些原因在中國很難開啟,所以 Google 專門為 Go 做了中國專用官網,在這裡。
你可以通過學習板塊,輸入上面給出的命令,在本地搭建一個 Go 語言即時學習環境,也可以點擊 Github Go Learn 來尋找一個你最喜歡的學習地址,Go by Example 很不錯。
如果你想找本相關的書學習,The Go Programming Languag 是不錯的一本。
至於更多的資料,援引知乎的文章系統學習GO,推薦幾本靠譜的書?
9. 誰在使用 Go
大量的公司都在積極嘗試使用 Go。這裡是一些名氣比較大的:
- Google:Kubernetes,MySQL scaling infrastructure,dl.google.com (Google下載服務)
- BaseCamp:Go at BaseCamp
- CloudFlare:Blog,ArsTechnica article
- CockroachDB:Why Go was the right choice for CockroachDB
- CoreOS:Github,Blog
- DataDog:Go at DataDog
- DigitalOcean:Get your development team started with Go
- Docker:Why did we decide to write Docker in Go
- Dropbox:Open Sourcing our Go Libraries
- Parse:How we moved our API from Ruby to Go and saved our Sanity
- Facebook:Github
- Intel:Github
- Iron.IO:Go after 2 years in Production
- MalwareBytes:Handling 1 million requests per minute with golang
- Medium:How Medium goes Social
- MongoDB:Go Agent
- Mozilla:Github
- Netflix:Github
- Pinterest:Github
- Segment:Github
- SendGrid:How to convince your company to go with Golang
- Shopify:Twitter
- SoundCloud:Go at SoundCloud
- SourceGraph:YouTuBe
- Twitter:Handling Five Billion Sessions a day in Real Time
- Uber:Github,Blog
在你 Go 之前
歡迎點贊 以及回複交流。如果本文有顯示不正確的地方,可以轉移到我的部落格Go 語言之美查看。