Golang與其他語言不同的九個特性
隨著編程語言的發展,Go 還很年輕。它於 2009 年 11 月 10 日首次發佈。其創建者Robert Griesemer
Rob Pike 和 Ken Thompson在 Google 工作,在那裡大規模擴展的挑戰激勵他們將 Go 設計為一種快速有效的編程解決方案,用於具有大型代碼庫、管理由多個開發人員,具有嚴格的性能要求,並跨越多個網絡和處理核心。
Go 的創始人在創建他們的新語言時也借此機會學習瞭其他編程語言的優點、缺點和漏洞。結果是一種幹凈、清晰和實用的語言,具有相對較少的命令和功能集。
在本文中,今天這篇文章將給大傢介紹一下 Go 與其他語言不同的 9 個特性。
1. Go 總是在構建中包含二進制文件
Go 運行時提供內存分配、垃圾收集、並發支持和網絡等服務。它被編譯到每個 Go 二進制文件中。這與許多其他語言不同,其中許多語言使用需要與程序一起安裝才能正常工作的虛擬機。
將運行時直接包含在二進制文件中使得分發和運行 Go 程序變得非常容易,並避免瞭運行時和程序之間的不兼容問題。Python、Ruby 和 JavaScript 等語言的虛擬機也沒有針對垃圾收集和內存分配進行優化,這解釋瞭 Go 相對於其他類似語言的優越速度。例如,Go 將盡可能多的存儲在堆棧中,其中數據按順序排列以便比堆更快地訪問。稍後會詳細介紹。
關於 Go 的靜態二進制文件的最後一件事是,因為不需要運行外部依賴項,所以它們啟動得非常快。如果您使用Google App Engine 之類的服務,這是一種在 Google Cloud 上運行的平臺即服務,它可以將您的應用程序縮減到零實例以節省雲成本,這將非常有用。當收到新請求時,App Engine 可以在眨眼間啟動 Go 程序的一個實例。在 Python 或 Node 中的相同體驗通常會導致 3-5 秒(或更長時間)的等待,因為所需的虛擬環境也會與新實例一起啟動。
2. Go 沒有針對程序依賴的集中托管服務
為瞭訪問已發佈的 Go 程序,開發人員不依賴集中托管的服務,例如Java 的Maven Central或JavaScript的NPM註冊表。相反,項目通過其源代碼存儲庫(最常見的是 Github)共享。在go install命令行允許以這種方式下載庫。
為什麼我喜歡這個功能?我一直認為像 Maven Central、PIP 和 NPM 這樣的集中托管的依賴服務有點令人生畏的黑盒子,也許可以抽象出下載和安裝依賴項的麻煩,但不可避免地會在依賴項錯誤時引發可怕的心跳停止發生。
此外,將的的模塊提供給其他人就像將其放入版本控制系統一樣簡單,這是分發程序的一種非常簡單的方式。
3. Go 是按值調用的
在 Go 中,當你提供一個原始值(數字、佈爾值或字符串)或一個結構體(類對象的粗略等價物)作為函數的參數時,Go 總是會復制變量的值。
在 Java、Python 和 JavaScript 等許多其他語言中,原語是按值傳遞的,但對象(類實例)是按引用傳遞的,這意味著接收函數實際上接收的是指向原始對象的指針,而不是其副本。在接收函數中對對象所做的任何更改都會反映在原始對象中。
在 Go 中,結構體和原語默認按值傳遞,可以選擇傳遞指針,通過使用星號運算符:
// 按值傳遞 func MakeNewFoo(f Foo ) (Foo, error) { f.Field1 = "New val" f.Field2 = f.Field2 + 1 return f, nil }
上述函數接收 Foo 的副本並返回一個新的 Foo 對象。
// 通過引用傳遞 func MutateFoo(f *Foo ) error { f.Field1 = "New val" f.Field2 = 2 return nil }
上面的函數接收一個指向 Foo 的指針並改變原始對象。
按值調用與按引用調用的這種明顯區別使您的意圖顯而易見,並降低瞭調用函數無意中改變傳入對象的可能性(當它不應該發生時(許多初學者開發人員很難做到這一點)握緊)。
正如麻省理工總結的:“可變性使得理解你的程序在做什麼變得更加困難,並且更難以執行契約”
此外,按值調用顯著減少瞭垃圾收集器的工作,這意味著應用程序更快、內存效率更高。這篇文章得出的結論是,指針追蹤(從堆中檢索指針值)比從連續堆棧中檢索值慢 10 到 20 倍。要記住的一個很好的經驗法則是:從內存中讀取的最快方法是順序讀取,這意味著將隨機存儲在 RAM 中的指針數量減少到最少。
4. ‘defer’ 關鍵字
在NodeJS 中,在我開始使用knex.js之前,我會通過創建一個數據庫池來手動管理我的代碼中的數據庫連接,然後在每個函數中從池中打開一個新連接,一旦所需的數據庫 CRUD 功能已完成。
這有點像維護噩夢,因為如果我沒有在每個函數結束時釋放連接,未釋放的數據庫連接的數量會慢慢增長,直到池中沒有更多可用連接,然後中斷應用程序。
現實情況是,程序經常需要釋放、清理和拆除資源、文件、連接等,因此 Go 引入瞭defer關鍵字作為管理這些的有效方式。
任何以defer開頭的語句都會延遲對它的調用,直到周圍的函數退出。這意味著您可以將清理/拆卸代碼放在函數的頂部(很明顯),知道一旦函數完成它就會如此。
func main() { if len(os.Args) < 2 { log.Fatal("no file specified") } f, err := os.Open(os.Args[1]) if err != nil { log.Fatal(err) } defer f.Close() data := make([]byte, 2048) for { count, err := f.Read(data) os.Stdout.Write(data[:count]) if err != nil { if err != io.EOF { log.Fatal(err) } break } } }
在上面的例子中,文件關閉方法被推遲瞭。我喜歡這種在函數頂部聲明你的內務處理意圖的模式,然後忘記它,知道一旦函數退出它就會完成它的工作。
5. Go 采用瞭函數式編程的最佳特性
函數式編程是一種高效且富有創造性的范式,幸運的是 Go 采用瞭函數式編程的最佳特性。在Go中:
- 函數是值,這意味著它們可以作為值添加到映射中,作為參數傳遞給其他函數,設置為變量,並從函數返回(稱為“高階函數”,在 Go 中經常使用裝飾器創建中間件圖案)。
- 可以創建和自動調用匿名函數。
- 在其他函數內聲明的函數允許閉包(在函數內聲明的函數能夠訪問和修改在外部函數中聲明的變量)。在慣用的 Go 中,閉包被廣泛使用來限制函數的范圍,並設置函數然後在其邏輯中使用的狀態。
func StartTimer (name string) func(){ t := time.Now() log.Println(name, "started") return func() { d := time.Now().Sub(t) log.Println(name, "took", d) } } func RunTimer() { stop := StartTimer("My timer") defer stop() time.Sleep(1 * time.Second) }
上面是一個閉包的例子。‘StartTimer’ 函數返回一個新函數,它通過閉包可以訪問在其出生范圍內設置的 ‘t’ 值。然後,此函數可以將當前時間與“t”的值進行比較,從而創建一個有用的計時器。感謝Mat Ryer的這個例子。
6. Go 有隱式接口
任何閱讀過有關SOLID編碼和設計模式的文獻的人都可能聽說過“優先組合勝過繼承”的口頭禪。簡而言之,這表明您應該將業務邏輯分解為不同的接口,而不是依賴於來自父類的屬性和邏輯的分層繼承。
另一個流行的方法是“為接口編程,而不是實現”: API 應該隻發佈其預期行為的契約(其方法簽名),而不是有關如何實現該行為的詳細信息。
這兩者都表明接口在現代編程中的重要性。
因此,Go 支持接口也就不足為奇瞭。事實上,接口是 Go 中唯一的抽象類型。
然而,與其他語言不同,Go 中的接口不是顯式實現的,而是隱式實現的。具體類型不聲明它實現瞭接口。相反,如果為該具體類型設置的方法集包含底層接口的所有方法集,則Go 認為該對象實現瞭 interface。
這種隱式接口實現(正式稱為結構類型)允許 Go 強制執行類型安全和解耦,保持動態語言中表現出的大部分靈活性。
相比之下,顯式接口將客戶端和實現綁定在一起,例如,在 Java 中替換依賴項比在 Go 中困難得多。
// 這是一個接口聲明(稱為Logic) type Logic interface { Process (data string) string } type LogicProvider struct {} // 這是 LogicProvider 上名為“Process”的方法 struct func (lp LogicProvider) Process (data string) string { // 業務邏輯 } // 這是具有 Logic 接口作為屬性的客戶端結構 type Client struct { L Logic } func(c Client) Program() { // 從某處獲取數據 cLProcess(data) } func main() { c := Client { L: LogicProvider{}, } c.Program() }
LogicProvider 中沒有任何聲明表示它符合Logic接口。這意味著客戶端將來可以輕松替換其邏輯提供程序,隻要該邏輯提供程序包含底層接口 ( Logic ) 的所有方法集。
7.錯誤處理
Go 中的錯誤處理方式與其他語言大不相同。簡而言之,Go 通過返回一個 error 類型的值作為函數的最後一個返回值來處理錯誤。
當函數按預期執行時,錯誤參數返回nil,否則返回錯誤值。調用函數然後檢查錯誤返回值,並處理錯誤,或拋出自己的錯誤。
// 函數返回一個整數和一個錯誤 func calculateRemainder(numerator int, denominator int) ( int, error ) { // if denominator == 0 { return 9, errors.New("denominator is 0" } // 沒有錯誤返回 return numerator / denominator, nil }
Go 以這種方式運行是有原因的:它迫使編碼人員考慮異常並正確處理它們。傳統的 try-catch 異常還會在代碼中添加至少一個新的代碼路徑,並以難以遵循的方式縮進代碼。Go 更喜歡將“快樂路徑”視為非縮進代碼,在“快樂路徑”完成之前識別並返回任何錯誤。
8.並發
可以說是 Go 最著名的特性,並發允許處理在機器或服務器上的可用內核數量上並行運行。當單獨的進程不相互依賴(不需要順序運行)並且時間性能至關重要時,並發性最有意義。這通常是 I/O 要求的情況,其中讀取或寫入磁盤或網絡的速度比除最復雜的內存中進程之外的所有進程慢幾個數量級。
函數調用前的“ go ”關鍵字將同時運行該函數。
func process(val int) int { // 用 val 做一些事情 } // 對於 'in' 中的每個值,同時運行 process 函數, // 並將 process 的結果讀取到 'out' func runConcurrently(in <-chan int, out chan<- int){ go func() { for val := range in { result := process(val) out <- result } } }
Go 中的並發是一項深入且相當高級的功能,但在有意義的地方,它提供瞭一種有效的方法來確保程序的最佳性能。
9. Go 標準庫
Go 有一個“包含電池”的理念,現代編程語言的許多要求都被納入標準庫,這使程序員的生活變得更加簡單。
如前所述,Go 是一種相對年輕的語言,這意味著標準庫中可以滿足現代應用程序的許多問題/要求。
一方面,Go 為網絡(特別是 HTTP/2)和文件管理提供瞭世界一流的支持。它還提供原生 JSON 編碼和解碼。因此,設置服務器來處理 HTTP 請求並返回響應(JSON 或其他)非常簡單,這解釋瞭 Go 在基於 REST 的 HTTP Web 服務開發中的流行。
正如Mat Ryer還指出的那樣,標準庫是開源的,是學習 Go 最佳實踐的絕佳方式。
如果你真的從這篇文章中學到瞭一些新東西,喜歡它,收藏它並與你的小夥伴分享。🤗最後,不要忘瞭❤或📑支持一下哦
到此這篇關於Golang與其他語言不同的九個特性的文章就介紹到這瞭,更多相關Golang 特性內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Golang 的defer執行規則說明
- Golang之defer 延遲調用操作
- Golang語言如何避免空指針引發的panic詳解
- go語言中http超時引發的事故解決
- golang進行簡單權限認證的實現