10個可以優(yōu)化代碼的Go語言技巧分享
在開發(fā)生產(chǎn)項目的過程中,我注意到經(jīng)常會發(fā)現(xiàn)自己在重復(fù)編寫代碼,使用某些技巧時沒有意識到,直到后來回顧工作時才意識到。
為了解決這個問題,我開發(fā)了一種解決方案,對我來說非常有幫助,我覺得對其他人也可能有用。
以下是一些從我的實用程序庫中隨機(jī)挑選的有用且多功能的代碼片段,沒有特定的分類或特定于系統(tǒng)的技巧。
1. 追蹤執(zhí)行時間的技巧
如果你想追蹤 Go 中函數(shù)的執(zhí)行時間,有一個簡單高效的技巧可以用一行代碼實現(xiàn),使用 defer
關(guān)鍵字即可。你只需要一個 TrackTime
函數(shù):
// Utility func TrackTime(pre time.Time) time.Duration { elapsed := time.Since(pre) fmt.Println("elapsed:", elapsed) return elapsed } func TestTrackTime(t *testing.T) { defer TrackTime(time.Now()) // <--- THIS time.Sleep(500 * time.Millisecond) } // 輸出: // elapsed: 501.11125ms
兩階段延遲執(zhí)行
Go 的 defer
不僅僅是用于清理任務(wù),還可以用于準(zhǔn)備任務(wù),考慮以下示例:
func setupTeardown() func() { fmt.Println("Run initialization") return func() { fmt.Println("Run cleanup") } } func main() { defer setupTeardown()() // <-------- fmt.Println("Main function called") } // 輸出: // Run initialization // Main function called // Run cleanup
這種模式的美妙之處在于,只需一行代碼,你就可以完成諸如以下任務(wù):
- 打開數(shù)據(jù)庫連接,然后關(guān)閉它。
- 設(shè)置模擬環(huán)境,然后拆除它。
- 獲取分布式鎖,然后釋放它。
- ...
"嗯,這似乎很聰明,但它在現(xiàn)實中有什么用處呢?"
還記得追蹤執(zhí)行時間的技巧嗎?我們也可以這樣做:
func TrackTime() func() { pre := time.Now() return func() { elapsed := time.Since(pre) fmt.Println("elapsed:", elapsed) } } func main() { defer TrackTime()() time.Sleep(500 * time.Millisecond) }
注意!如果我連接到數(shù)據(jù)庫時出現(xiàn)錯誤怎么辦?
確實,像 defer TrackTime()
或 defer ConnectDB()
這樣的模式不會妥善處理錯誤。這種技巧最適合用于測試或者當(dāng)你愿意冒著致命錯誤的風(fēng)險時使用,參考下面這種面向測試的方法:
func TestSomething(t *testing.T) { defer handleDBConnection(t)() // ... } func handleDBConnection(t *testing.T) func() { conn, err := connectDB() if err != nil { t.Fatal(err) } return func() { fmt.Println("Closing connection", conn) } }
這樣,在測試期間可以處理數(shù)據(jù)庫連接的錯誤。
2. 預(yù)分配切片
根據(jù)文章《Go 性能提升技巧》中的見解,預(yù)分配切片或映射可以顯著提高 Go 程序的性能。
但是值得注意的是,如果我們不小心使用 append
而不是索引(如 a[i]
),這種方法有時可能導(dǎo)致錯誤。你知道嗎,我們可以在不指定數(shù)組長度(為零)的情況下使用預(yù)分配的切片,就像在上述文章中解釋的那樣?這使我們可以像使用 append
一樣使用預(yù)分配的切片:
// 與其 a := make([]int, 10) a[0] = 1 // 不如這樣使用 b := make([]int, 0, 10) b = append(b, 1)
3. 鏈?zhǔn)秸{(diào)用
鏈?zhǔn)秸{(diào)用技術(shù)可以應(yīng)用于函數(shù)(指針)接收器。為了說明這一點,讓我們考慮一個 Person
結(jié)構(gòu),它有兩個函數(shù) AddAge
和 Rename
,用于對其進(jìn)行修改。
type Person struct { Name string Age int } func (p *Person) AddAge() { p.Age++ } func (p *Person) Rename(name string) { p.Name = name }
如果你想給一個人增加年齡然后給他們改名字,常規(guī)的方法是:
func main() { p := Person{Name: "Aiden", Age: 30} p.AddAge() p.Rename("Aiden 2") }
或者,我們可以修改 AddAge
和 Rename
函數(shù)接收器,使其返回修改后的對象本身,即使它們通常不返回任何內(nèi)容。
func (p *Person) AddAge() *Person { p.Age++ return p } func (p *Person) Rename(name string) *Person { p.Name = name return p }
通過返回修改后的對象本身,我們可以輕松地將多個函數(shù)接收器鏈在一起,而無需添加不必要的代碼行:
p = p.AddAge().Rename("Aiden 2")
4. Go 1.20 允許將切片解析為數(shù)組或數(shù)組指針
當(dāng)我們需要將切片轉(zhuǎn)換為固定大小的數(shù)組時,不能直接賦值,例如:
a := []int{0, 1, 2, 3, 4, 5} var b [3]int = a[0:3] // 在變量聲明中不能將 a[0:3](類型為 []int 的值)賦值給 [3]int 類型的變量 // (不兼容的賦值)
為了將切片轉(zhuǎn)換為數(shù)組,Go 團(tuán)隊在 Go 1.17 中更新了這個特性。隨著 Go 1.20 的發(fā)布,借助更方便的字面量,轉(zhuǎn)換過程變得更加簡單:
// Go 1.20 func Test(t *testing.T) { a := []int{0, 1, 2, 3, 4, 5} b := [3]int(a[0:3]) fmt.Println(b) // [0 1 2] } // Go 1.17 func TestM2e(t *testing.T) { a := []int{0, 1, 2, 3, 4, 5} b := *(*[3]int)(a[0:3]) fmt.Println(b) // [0 1 2] }
只是一個快速提醒:你可以使用 a[:3] 替代 a[0:3]。我提到這一點是為了更清晰地說明。
5. 使用 "import _" 進(jìn)行包初始化
有時,在庫中,你可能會遇到結(jié)合下劃線 (_) 的導(dǎo)入語句,如下所示:
import ( _ "google.golang.org/genproto/googleapis/api/annotations" )
這將執(zhí)行包的初始化代碼(init 函數(shù)),而無需為其創(chuàng)建名稱引用。這允許你在運行代碼之前初始化包、注冊連接和執(zhí)行其他任務(wù)。
讓我們通過一個示例來更好地理解它的工作原理:
// 下劃線 package underscore func init() { fmt.Println("init called from underscore package") } // main package main import ( _ "lab/underscore" ) func main() {} // 輸出:init called from underscore package
6. 使用 "import ." 進(jìn)行導(dǎo)入
在了解了如何使用下劃線進(jìn)行導(dǎo)入后,讓我們看看如何更常見地使用點 (.) 運算符。
作為開發(fā)者,點 (.) 運算符可用于在不必指定包名的情況下使用導(dǎo)入包的導(dǎo)出標(biāo)識符,這對于懶惰的開發(fā)者來說是一個有用的快捷方式。
很酷,對吧?這在處理項目中的長包名時特別有用,比如 externalmodel
或 doingsomethinglonglib
。
為了演示,這里有一個簡單的例子:
package main import ( "fmt" . "math" ) func main() { fmt.Println(Pi) // 3.141592653589793 fmt.Println(Sin(Pi / 2)) // 1 }
7. Go 1.20 允許將多個錯誤合并為單個錯誤
Go 1.20 引入了對錯誤包的新功能,包括對多個錯誤的支持以及對 errors.Is 和 errors.As 的更改。
在 errors
中添加的一個新函數(shù)是 Join
,我們將在下面詳細(xì)討論它:
var ( err1 = errors.New("Error 1st") err2 = errors.New("Error 2nd") ) func main() { err := err1 err = errors.Join(err, err2) fmt.Println(errors.Is(err, err1)) // true fmt.Println(errors.Is(err, err2)) // true }
如果有多個任務(wù)導(dǎo)致錯誤,你可以使用 Join
函數(shù)而不是手動管理數(shù)組。這簡化了錯誤處理過程。
8. 檢查接口是否為真正的 nil
即使接口持有的值為 nil
,也不意味著接口本身為 nil
。這可能導(dǎo)致 Go 程序中的意外錯誤。因此,重要的是要知道如何檢查接口是否為真正的 nil
。
func main() { var x interface{} var y *int = nil x = y if x != nil { fmt.Println("x != nil") // <-- 實際輸出 } else { fmt.Println("x == nil") } fmt.Println(x) } // 輸出: // x != nil // <nil>
我們?nèi)绾未_定 interface{}
值是否為 nil
呢?幸運的是,有一個簡單的工具可以幫助我們實現(xiàn)這一點:
func IsNil(x interface{}) bool { if x == nil { return true } return reflect.ValueOf(x).IsNil() }
9. 在 JSON 中解析 time.Duration
當(dāng)解析 JSON 時,使用 time.Duration
可能是一個繁瑣的過程,因為它需要在一秒的后面添加 9 個零(即 1000000000)。為了簡化這個過程,我創(chuàng)建了一個名為 Duration
的新類型:
type Duration time.Duration
為了將字符串(如 "1s" 或 "20h5m")解析為 int64 類型的持續(xù)時間,我還為這個新類型實現(xiàn)了自定義的解析邏輯:
func (d *Duration) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } dur, err := time.ParseDuration(s) if err != nil { return err } *d = Duration(dur) return nil }
但是,需要注意的是,變量 'd' 不應(yīng)為 nil,否則可能會導(dǎo)致編組錯誤。或者,你還可以在函數(shù)開頭對 'd' 進(jìn)行檢查。
10. 避免裸參數(shù)
當(dāng)處理具有多個參數(shù)的函數(shù)時,僅通過閱讀其用法來理解每個參數(shù)的含義可能會令人困惑??紤]以下示例:
printInfo("foo", true, true)
如果不檢查 printInfo
函數(shù),那么第一個 'true' 和第二個 'true' 的含義是什么呢?當(dāng)你有一個具有多個參數(shù)的函數(shù)時,僅通過閱讀其用法來理解參數(shù)的含義可能會令人困惑。
但是,我們可以使用注釋使代碼更易讀。例如:
// func printInfo(name string, isLocal, done bool) printInfo("foo", true /* isLocal */, true /* done */)
有些 IDE 也支持這個功能,可以在函數(shù)調(diào)用建議中顯示注釋,但可能需要在設(shè)置中啟用。
到此這篇關(guān)于10個可以優(yōu)化代碼的Go語言技巧分享的文章就介紹到這了,更多相關(guān)Go技巧內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang Map實現(xiàn)賦值和擴(kuò)容的示例代碼
這篇文章主要介紹了Golang Map實現(xiàn)賦值和擴(kuò)容的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04Go1.16新特性embed打包靜態(tài)資源文件實現(xiàn)
這篇文章主要為大家介紹了Go?1.16新特性embed打包靜態(tài)資源文件的實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07解決golang post文件時Content-Type出現(xiàn)的問題
這篇文章主要介紹了解決golang post文件時Content-Type出現(xiàn)的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-05-05詳解Go多協(xié)程并發(fā)環(huán)境下的錯誤處理
這篇文章主要介紹了詳解Go多協(xié)程并發(fā)環(huán)境下的錯誤處理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08

一文告訴你大神是如何學(xué)習(xí)Go語言之make和new