🌊 博主簡介:CSDN原力作者,華為云享專家,掘金優秀作者
🌊 個人博客:haiyong.site
🌊 粉絲專屬福利:簡歷模板、PPT模板、學習資料、面試題庫,文末領取
直接跳到末尾領取資料
隨著編程語言的發展,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 最佳實踐的絕佳方式,
如果你真的從這篇文章中學到了一些新東西,喜歡它,收藏它并與你的小伙伴分享,🤗最后,不要忘了?或📑支持一下哦
🌊 粉絲福利:超好玩推文定期粉絲抽獎福利
🌊 行業資料:精品PPT模板幾千套,簡歷模板一千多套
🌊 面試題庫:Java核心知識點大全和面試真題資料
🌊 學習資料:2300套PHP建站原始碼,微信小程式入門資料,Python全集(400集)
可通過下方公眾號回復【資源】獲取 👇🏻👇🏻👇🏻
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/302235.html
標籤:其他
