摘要:文将詳細介紹 Golang 的語言特點以及它的優缺點和适用場景,帶着上述幾個疑問,為讀者分析 Go 語言的各個方面,以幫助初入 IT 行業的程式員以及對 Go 感興趣的開發者進一步了解這個熱門語言。
本文分享自華為雲社群《大紅大紫的 Golang 真的是後端開發中的萬能藥嗎?》,原文作者:Marvin Zhang 。
前言
城外的人想進去,城裡的人想出來。-- 錢鐘書《圍城》
随着容器編排(Container Orchestration)、微服務(Micro Services)、雲技術(Cloud Technology)等在 IT 行業不斷盛行,2009 年誕生于 Google 的 Golang(Go 語言,簡稱 Go)越來越受到軟體工程師的歡迎和追捧,成為如今炙手可熱的後端程式設計語言。在用 Golang 開發的軟體項目清單中,有 Docker(容器技術)、Kubernetes(容器編排)這樣的颠覆整個 IT 行業的明星級産品,也有像 Prometheus(監控系統)、Etcd(分布式存儲)、InfluxDB(時序資料庫)這樣的強大實用的知名項目。當然,Go 語言的應用領域也絕不局限于容器和分布式系統。如今很多大型網際網路企業在大量使用 Golang 建構後端 Web 應用,例如今日頭條、京東、七牛雲等;長期被 Python 統治的架構爬蟲領域也因為簡單而易用的爬蟲架構 Colly 的崛起而不斷受到 Golang 的挑戰。Golang 已經成為了如今大多數軟體工程師最想學習的程式設計語言。下圖是 HackerRank 在 2020 年調查程式員技能的相關結果。

那麼,Go 語言真的是後端開發人員的救命良藥呢?它是否能夠有效提高程式員們的技術實力和開發效率,進而幫助他們在職場上更進一步呢?Go 語言真的值得我們花大量時間深入學習麼?本文将詳細介紹 Golang 的語言特點以及它的優缺點和适用場景,帶着上述幾個疑問,為讀者分析 Go 語言的各個方面,以幫助初入 IT 行業的程式員以及對 Go 感興趣的開發者進一步了解這個熱門語言。
Golang 簡介
Golang 誕生于網際網路巨頭 Google,而這并不是一個巧合。我們都知道,Google 有一個 20% 做業餘項目(Side Project)的企業文化,允許工程師們能夠在輕松的環境下創造一些具有颠覆性創新的産品。而 Golang 也正是在這 20% 時間中不斷孵化出來。Go 語言的創始者也是 IT 界内大名鼎鼎的行業領袖,包括 Unix 核心團隊成員 Rob Pike、C 語言作者 Ken Thompson、V8 引擎核心貢獻者 Robert Griesemer。Go 語言被大衆所熟知還是源于容器技術 Docker 在 2014 年被開源後的爆發式發展。之後,Go 語言因為其簡單的文法以及迅猛的編譯速度受到大量開發者的追捧,也誕生了很多優秀的項目,例如 Kubernetes。
Go 語言相對于其他傳統熱門程式設計語言來說,有很多優點,特别是其高效編譯速度和天然并發特性,讓其成為快速開發分布式應用的首選語言。Go 語言是靜态類型語言,也就是說 Go 語言跟 Java、C# 一樣需要編譯,而且有完備的類型系統,可以有效減少因類型不一緻導緻的代碼品質問題。是以,Go 語言非常适合建構對穩定性和靈活性均有要求的大型 IT 系統,這也是很多大型網際網路公司用 Golang 重構老代碼的重要原因:傳統的靜态 OOP 語言(例如 Java、C#)穩定性高但缺乏靈活性;而動态語言(例如 PHP、Python、Ruby、Node.js)靈活性強但缺乏穩定性。是以,“熊掌和魚兼得” 的 Golang,受到開發者們的追捧是自然而然的事情,畢竟,“天下苦 Java/PHP/Python/Ruby 們久矣“。
不過,Go 語言并不是沒有缺點。用辯證法的思維方式可以推測,Golang 的一些突出特性将成為它的雙刃劍。例如,Golang 文法簡單的優勢特點将限制它處理複雜問題的能力。尤其是 Go 語言缺乏泛型(Generics)的問題,導緻它建構通用架構的複雜度大增。雖然這個突出問題在 2.0 版本很可能會有效解決,但這也反映出來明星程式設計語言也會有缺點。當然,Go 的缺點還不止于此,Go 語言使用者還會吐槽其啰嗦的錯誤處理方式(Error Handling)、缺少嚴格限制的鴨子類型(Duck Typing)、日期格式問題等。下面,我們将從 Golang 語言特點開始,由淺入深多元度深入分析 Golang 的優缺點以及項目适用場景。
語言特點
簡潔的文法特征
Go 語言的文法非常簡單,至少在變量聲明、結構體聲明、函數定義等方面顯得非常簡潔。
變量的聲明不像 Java 或 C 那樣啰嗦,在 Golang 中可以用 := 這個文法來聲明新變量。例如下面這個例子,當你直接使用 := 來定義變量時,Go 會自動将指派對象的類型聲明為指派來源的類型,這節省了大量的代碼。
func main() {
valInt := 1 // 自動推斷 int 類型
valStr := "hello" // 自動推斷為 string 類型
valBool := false // 自動推斷為 bool 類型
}
Golang 還有很多幫你節省代碼的地方。你可以發現 Go 中不會強制要求用 new 這個關鍵詞來生成某個類(Class)的新執行個體(Instance)。而且,對于公共和私有屬性(變量和方法)的約定不再使用傳統的 public 和 private 關鍵詞,而是直接用屬性變量首字母的大小寫來區分。下面一些例子可以幫助讀者了解這些特點。
// 定義一個 struct 類
type SomeClass struct {
PublicVariable string // 公共變量
privateVariable string // 私有變量
}
// 公共方法
func (c *SomeClass) PublicMethod() (result string) {
return "This can be called by external modules"
}
// 私有方法
func (c *SomeClass) privateMethod() (result string) {
return "This can only be called in SomeClass"
}
func main() {
// 生成執行個體
someInstance := SomeClass{
PublicVariable: "hello",
privateVariable: "world",
}
}
如果你用 Java 來實作上述這個例子,可能會看到冗長的 .java 類檔案,例如這樣。
// SomeClass.java
public SomeClass {
public String PublicVariable; // 公共變量
private String privateVariable; // 私有變量
// 構造函數
public SomeClass(String val1, String val2) {
this.PublicVariable = val1;
this.privateVariable = val2;
}
// 公共方法
public String PublicMethod() {
return "This can be called by external modules";
}
// 私有方法
public String privateMethod() {
return "This can only be called in SomeClass";
}
}
...
// Application.java
public Application {
public static void main(String[] args) {
// 生成執行個體
SomeClass someInstance = new SomeClass("hello", "world");
}
}
可以看到,在 Java 代碼中除了容易看花眼的多層花括号以外,還充斥着大量的 public、private、static、this 等修飾用的關鍵詞,顯得異常啰嗦;而 Golang 代碼中則靠簡單的約定,例如首字母大小寫,避免了很多重複性的修飾詞。當然,Java 和 Go 在類型系統上還是有一些差別的,這也導緻 Go 在處理複雜問題顯得有些力不從心,這是後話,後面再讨論。總之,結論就是 Go 的文法在靜态類型程式設計語言中非常簡潔。
内置并發程式設計
Go 語言之是以成為分布式應用的首選,除了它性能強大以外,其最主要的原因就是它天然的并發程式設計。這個并發程式設計特性主要來自于 Golang 中的協程(Goroutine)和通道(Channel)。下面是使用協程的一個例子。
func asyncTask() {
fmt.Printf("This is an asynchronized task")
}
func syncTask() {
fmt.Printf("This is a synchronized task")
}
func main() {
go asyncTask() // 異步執行,不阻塞
syncTask() // 同步執行,阻塞
go asyncTask() // 等待前面 syncTask 完成之後,再異步執行,不阻塞
}
可以看到,關鍵詞 go 加函數調用可以讓其作為一個異步函數執行,不會阻塞後面的代碼。而如果不加 go 關鍵詞,則會被當成是同步代碼執行。如果讀者熟悉 JavaScript 中的 async/await、Promise 文法,甚至是 Java、Python 中的多線程異步程式設計,你會發現它們跟 Go 異步程式設計的簡單程度不是一個量級的!
異步函數,也就是協程之間的通信可以用 Go 語言特有的通道來實作。下面是關于通道的一個例子。
func longTask(signal chan int) {
// 不帶參數的 for
// 相當于 while 循環
for {
// 接收 signal 通道傳值
v := <- signal
// 如果接收值為 1,停止循環
if v == 1 {
break
}
time.Sleep(1 * Second)
}
}
func main() {
// 聲明通道
sig := make(chan int)
// 異步調用 longTask
go longTask(sig)
// 等待 1 秒鐘
time.Sleep(1 * time.Second)
// 向通道 sig 傳值
sig <- 1
// 然後 longTask 會接收 sig 傳值,終止循環
}
面向接口程式設計
Go 語言不是嚴格的面向對象程式設計(OOP),它采用的是面向接口程式設計(IOP),是相對于 OOP 更先進的程式設計模式。作為 OOP 體系的一部分,IOP 更加強調規則和限制,以及接口類型方法的約定,進而讓開發人員盡可能的關注更抽象的程式邏輯,而不是在更細節的實作方式上浪費時間。很多大型項目采用的都是 IOP 的程式設計模式。如果想了解更多面向接口程式設計,請檢視 “碼之道” 個人技術部落格的往期文章《為什麼說 TypeScript 是開發大型前端項目的必備語言》,其中有關于面向接口程式設計的詳細講解。
Go 語言跟 TypeScript 一樣,也是采用鴨子類型的方式來校驗接口繼承。下面這個例子可以描述 Go 語言的鴨子類型特性。
// 定義 Animal 接口
interface Animal {
Eat() // 聲明 Eat 方法
Move() // 聲明 Move 方法
}
// ==== 定義 Dog Start ====
// 定義 Dog 類
type Dog struct {
}
// 實作 Eat 方法
func (d *Dog) Eat() {
fmt.Printf("Eating bones")
}
// 實作 Move 方法
func (d *Dog) Move() {
fmt.Printf("Moving with four legs")
}
// ==== 定義 Dog End ====
// ==== 定義 Human Start ====
// 定義 Human 類
type Human struct {
}
// 實作 Eat 方法
func (h *Human) Eat() {
fmt.Printf("Eating rice")
}
// 實作 Move 方法
func (h *Human) Move() {
fmt.Printf("Moving with two legs")
}
// ==== 定義 Human End ====
可以看到,雖然 Go 語言可以定義接口,但跟 Java 不同的是,Go 語言中沒有顯示聲明接口實作(Implementation)的關鍵詞修飾文法。在 Go 語言中,如果要繼承一個接口,你隻需要在結構體中實作該接口聲明的所有方法。這樣,對于 Go 編譯器來說你定義的類就相當于繼承了該接口。在這個例子中,我們規定,隻要既能吃(Eat)又能活動(Move)的東西就是動物(Animal)。而狗(Dog)和人(Human)恰巧都可以吃和動,是以它們都被算作動物。這種依靠實作方法比對度的繼承方式,就是鴨子類型:如果一個動物看起來像鴨子,叫起來也像鴨子,那它一定是鴨子。這種鴨子類型相對于傳統 OOP 程式設計語言顯得更靈活。但是,後面我們會讨論到,這種程式設計方式會帶來一些麻煩。
錯誤處理
Go 語言的錯誤處理是臭名昭著的啰嗦。這裡先給一個簡單例子。
package main
import "fmt"
func isValid(text string) (valid bool, err error){
if text == "" {
return false, error("text cannot be empty")
}
return text == "valid text", nil
}
func validateForm(form map[string]string) (res bool, err error) {
for _, text := range form {
valid, err := isValid(text)
if err != nil {
return false, err
}
if !valid {
return false, nil
}
}
return true, nil
}
func submitForm(form map[string]string) (err error) {
if res, err := validateForm(form); err != nil || !res {
return error("submit error")
}
fmt.Printf("submitted")
return nil
}
func main() {
form := map[string]string{
"field1": "",
"field2": "invalid text",
"field2": "valid text",
}
if err := submitForm(form); err != nil {
panic(err)
}
}
雖然上面整個代碼是虛構的,但可以從中看出,Go 代碼中充斥着 if err := ...; err != nil { ... } 之類的錯誤判斷語句。這是因為 Go 語言要求開發者自己管理錯誤,也就是在函數中的錯誤需要顯式抛出來,否則 Go 程式不會做任何錯誤處理。因為 Go 沒有傳統程式設計語言的 try/catch 針對錯誤處理的文法,是以在錯誤管理上缺少靈活度,導緻了 “err 滿天飛” 的局面。
不過,辯證法則告訴我們,這種做法也是有好處的。第一,它強制要求 Go 語言開發者從代碼層面來規範錯誤的管理方式,這驅使開發者寫出更健壯的代碼;第二,這種顯式傳回錯誤的方式避免了 “try/catch 一把梭”,因為這種 “一時爽” 的做法很可能導緻 Bug 無法準确定位,進而産生很多不可預測的問題;第三,由于沒有 try/catch 的括号或額外的代碼塊,Go 程式代碼整體看起來更清爽,可讀性較強。
其他
Go 語言肯定還有很多其他特性,但筆者認為以上的特性是 Go 語言中比較有特色的,是區分度比較強的特性。Go 語言其他一些特性還包括但不限于如下内容。
- 編譯迅速
- 跨平台
- defer 延遲執行
- select/case 通道選擇
- 直接編譯成可執行程式
- 非正常依賴管理(可以直接引用 Github 倉庫作為依賴,例如
)import "github.com/crawlab-team/go-trace"
- 非正常日期格式(格式為 “2006-01-02 15:04:05”,你沒看錯,據說這就是 Golang 的創始時間!)
優缺點概述
前面介紹了 Go 的很多語言特性,想必讀者已經對 Golang 有了一些基本的了解。其中的一些語言特性也暗示了它相對于其他程式設計語言的優缺點。Go 語言雖然現在很火,在稱贊并擁抱 Golang 的同時,不得不了解它的一些缺點。
這裡筆者不打算長篇大論的解析 Go 語言的優劣,而是将其中相關的一些事實列舉出來,讀者可以自行判斷。以下是筆者總結的 Golang 語言特性的不完整優缺點對比清單。
其實,每一個特性在某種情境下都有其相應的優勢和劣勢,不能一概而論。就像 Go 語言采用的靜态類型和面向接口程式設計,既不缺少類型限制,也不像嚴格 OOP 那樣冗長繁雜,是介于動态語言和傳統靜态類型 OOP 語言之間的現代程式設計語言。這個定位在提升 Golang 開發效率的同時,也閹割了不少必要 OOP 文法特性,進而缺乏快速建構通用工程架構的能力(這裡不是說 Go 無法建構通用架構,而是它沒有 Java、C# 這麼容易)。另外,Go 語言 “奇葩” 的錯誤處理規範,讓 Go 開發者們又愛又恨:可以開發出更健壯的應用,但同時也犧牲了一部分代碼的簡潔性。要知道,Go 語言的設計理念是為了 “大道至簡”,是以才會在追求高性能的同時設計得盡可能簡單。
無可否認的是,Go 語言内置的并發支援是非常近年來非常創新的特性,這也是它被分布式系統廣泛采用的重要原因。同時,它相對于動辄編譯十幾分鐘的 Java 來說是非常快的。此外,Go 語言沒有因為文法簡單而犧牲了穩定性;相反,它從簡單的限制規範了整個 Go 項目代碼風格。是以,**“快”(Fast)、“簡”(Concise)、“穩”(Robust)**是 Go 語言的設計目的。我們在對學習 Golang 的過程中不能無腦的接納它的一切,而是應該根據它自身的特性判斷在實際項目應用中的情況。
适用場景
經過前文關于 Golang 各個次元的讨論,我們可以得出結論:Go 語言并不是後端開發的萬能藥。在實際開發工作中,開發者應該避免在任何情況下無腦使用 Golang 作為後端開發語言。相反,工程師在決定技術選型之前應該全面了解候選技術(語言、架構或架構)的方方面面,包括候選技術與業務需求的切合度,與開發團隊的融合度,以及其學習、開發、時間成本等因素。筆者在學習了包括前後端的一些程式設計語言之後,發現它們各自有各自的優勢,也有相應的劣勢。如果一門程式設計語言能廣為人知,那它絕對不會是一門糟糕語言。是以,筆者不會斷言 “XXX 是世界上最好的語言“,而是給讀者分享個人關于特定應用場景下技術選型的思路。當然,本文是針對 Go 語言的技術文,接下來筆者将分享一下個人認為 Golang 最适合的應用場景。
分布式應用
Golang 是非常适合在分布式應用場景下開發的。分布式應用的主要目的是盡可能多的利用計算資源和網絡帶寬,以求最大化系統的整體性能和效率,其中重要的需求功能就是并發(Concurrency)。而 Go 是支援高并發和異步程式設計方面的佼佼者。
前面已經提到,Go 語言内置了協程(Goroutine)和通道(Channel)兩大并發特性,這使後端開發者進行異步程式設計變得非常容易。Golang 中還内置了sync 庫,包含 Mutex(互斥鎖)、WaitGroup(等待組)、Pool(臨時對象池)等接口,幫助開發者在并發程式設計中能更安全的掌控 Go 程式的并發行為。Golang 還有很多分布式應用開發工具,例如分布式儲存系統(Etcd、SeaweedFS)、RPC 庫(gRPC、Thrift)、主流資料庫 SDK(mongo-driver、gnorm、redigo)等。這些都可以幫助開發者有效的建構分布式應用。
網絡爬蟲
稍微了解網絡爬蟲的開發者應該會聽說過 Scrapy,再不濟也是 Python。市面上關于 Python 網絡爬蟲的技術書籍數不勝數,例如崔慶才的《Python 3 網絡開發實戰》和韋世東的《Python 3 網絡爬蟲寶典》。用 Python 編寫的高性能爬蟲架構 Scrapy,自釋出以來一直是爬蟲工程師的首選。
不過,由于近期 Go 語言的迅速發展,越來越多的爬蟲工程師注意到用 Golang 開發網路爬蟲的巨大優勢。其中,用 Go 語言編寫的 Colly 爬蟲架構,如今在 Github 上已經有 13k+ 标星。其簡潔的 API 以及高效的采集速度,吸引了很多爬蟲工程師,占據了爬蟲界一哥 Scrapy 的部分份額。前面已經提到,Go 語言内置的并發特性讓嚴重依賴網絡帶寬的爬蟲程式更加高效,很大的提高了資料采集效率。另外,Go 語言作為靜态語言,相對于動态語言 Python 來說有更好的限制下,是以健壯性和穩定性都更好。
後端 API
Golang 有很多優秀的後端架構,它們大部分都非常完備的支援了現代後端系統的各種功能需求:RESTful API、路由、中間件、配置、鑒權等子產品。而且用 Golang 寫的後端應用性能很高,通常有非常快的響應速度。筆者曾經在開源爬蟲管理平台 Crawlab 中用 Golang 重構了 Python 的後端 API,響應速度從之前的幾百毫秒優化到了幾十毫秒甚至是幾毫秒,用實踐證明 Go 語言在後端性能方面全面碾壓動态語言。Go 語言中比較知名的後端架構有 Gin、Beego、Echo、Iris。
當然,這裡并不是說用 Golang 寫後端就完全是一個正确的選擇。筆者在工作中會用到 Java 和 C#,用了各自的主流架構(SpringBoot 和 .Net Core)之後,發現這兩門傳統 OOP 語言雖然文法啰嗦,但它們的文法特性很豐富,特别是泛型,能夠輕松應對一些邏輯複雜、重複性高的業務需求。是以,筆者認為在考慮用 Go 來編寫後端 API 時候,可以提前調研一下 Java 或 C#,它們在寫後端業務功能方面做得非常棒。
總結
本篇文章從 Go 語言的主要文法特性入手,循序漸進分析了 Go 語言作為後端程式設計語言的優點和缺點,以及其在實際軟體項目開發中的試用場景。筆者認為 Go 語言與其他語言的主要差別在于文法簡潔、天然支援并發、面向接口程式設計、錯誤處理等方面,并且對各個語言特性在正反兩方面進行了分析。最後,筆者根據之前的分析内容,得出了 Go 語言作為後端開發程式設計語言的适用場景,也就是分布式應用、網絡爬蟲以及後端API。
當然,Go 語言的實際應用領域還不限于此。實際上,不少知名資料庫都是用 Golang 開發的,例如時序資料庫 Prometheus 和 InfluxDB、以及有 NewSQL 之稱的 TiDB。此外,在機器學習方面,Go 語言也有一定的優勢,隻是目前來說,Google 因為 Swift 跟 TensorFlow 的意向合作,似乎還沒有大力推廣 Go 在機器學習方面的應用,不過一些潛在的開源項目已經湧現出來,例如 GoLearn、GoML、Gorgonia 等。
在了解 Go 語言的優勢和适用場景的同時,我們必須意識到 Go 語言并不是全能的。它相較于其他一些主流架構來說也有一些缺點。開發者在準備采用 Go 作為實際工作開發語言的時候,需要全面了解其語言特性,進而做出最合理的技術選型。就像打網球一樣,不僅需要掌握正反手,還要會發球、高壓球、截擊球等技術動作,這樣才能把網球打好。
點選關注,第一時間了解華為雲新鮮技術~