關(guān)于Go你不得不知道的一些實用小技巧
Go 箴言
- 不要通過共享內(nèi)存進(jìn)行通信,通過通信共享內(nèi)存
- 并發(fā)不是并行
- 管道用于協(xié)調(diào);互斥量(鎖)用于同步
- 接口越大,抽象就越弱
- 利用好零值
- 空接口
interface{}沒有任何類型約束 - Gofmt 的風(fēng)格不是人們最喜歡的,但 gofmt 是每個人的最愛
- 允許一點點重復(fù)比引入一點點依賴更好
- 系統(tǒng)調(diào)用必須始終使用構(gòu)建標(biāo)記進(jìn)行保護(hù)
- 必須始終使用構(gòu)建標(biāo)記保護(hù) Cgo
- Cgo 不是 Go
- 使用標(biāo)準(zhǔn)庫的
unsafe包,不能保證能如期運行 - 清晰比聰明更好
- 反射永遠(yuǎn)不清晰
- 錯誤是值
- 不要只檢查錯誤,還要優(yōu)雅地處理它們
- 設(shè)計架構(gòu),命名組件,(文檔)記錄細(xì)節(jié)
- 文檔是供用戶使用的
- 不要(在生產(chǎn)環(huán)境)使用
panic()
Go 之禪
- 每個 package 實現(xiàn)單一的目的
- 顯式處理錯誤
- 盡早返回,而不是使用深嵌套
- 讓調(diào)用者處理并發(fā)(帶來的問題)
- 在啟動一個 goroutine 時,需要知道何時它會停止
- 避免 package 級別的狀態(tài)
- 簡單很重要
- 編寫測試以鎖定 package API 的行為
- 如果你覺得慢,先編寫 benchmark 來證明
- 適度是一種美德
- 可維護(hù)性
代碼
使用 go fmt 格式化
讓團(tuán)隊一起使用官方的 Go 格式工具,不要重新發(fā)明輪子。
嘗試減少代碼復(fù)雜度。 這將幫助所有人使代碼易于閱讀。
多個 if 語句可以折疊成 switch
// NOT BAD
if foo() {
// ...
} else if bar == baz {
// ...
} else {
// ...
}
// BETTER
switch {
case foo():
// ...
case bar == baz:
// ...
default:
// ...
}用 chan struct{} 來傳遞信號, chan bool 表達(dá)的不夠清楚
當(dāng)你在結(jié)構(gòu)中看到 chan bool 的定義時,有時不容易理解如何使用該值,例如:
type Service struct {
deleteCh chan bool // what does this bool mean?
}但是我們可以將其改為明確的 chan struct {} 來使其更清楚:我們不在乎值(它始終是 struct {}),我們關(guān)心可能發(fā)生的事件,例如:
type Service struct {
deleteCh chan struct{} // ok, if event than delete something.
}30 * time.Second 比 time.Duration(30) * time.Second 更好
你不需要將無類型的常量包裝成類型,編譯器會找出來。
另外最好將常量移到第一位:
// BAD delay := time.Second * 60 * 24 * 60 // VERY BAD delay := 60 * time.Second * 60 * 24 // GOOD delay := 24 * 60 * 60 * time.Second
用 time.Duration 代替 int64 + 變量名
// BAD var delayMillis int64 = 15000 // GOOD var delay time.Duration = 15 * time.Second
按類型分組 const 聲明,按邏輯和/或類型分組 var
// BAD
const (
foo = 1
bar = 2
message = "warn message"
)
// MOSTLY BAD
const foo = 1
const bar = 2
const message = "warn message"
// GOOD
const (
foo = 1
bar = 2
)
const message = "warn message"這個模式也適用于 var。
** 每個阻塞或者 IO 函數(shù)操作應(yīng)該是可取消的或者至少是可超時的
** 為整型常量值實現(xiàn)
Stringer接口** 檢查
defer中的錯誤
defer func() {
err := ocp.Close()
if err != nil {
rerr = err
}
}()** 不要在
checkErr函數(shù)中使用panic()或os.Exit()** 僅僅在很特殊情況下才使用 panic, 你必須要去處理 error
** 不要給枚舉使用別名,因為這打破了類型安全
package main
type Status = int
type Format = int // remove `=` to have type safety
const A Status = 1
const B Format = 1
func main() {
println(A == B)
}**
如果你想省略返回參數(shù),你最好表示出來
_ = f()比f()更好
**
我們用
a := []T{}來簡單初始化 slice**
用 range 循環(huán)來進(jìn)行數(shù)組或 slice 的迭代
for _, c := range a[3:7] {...}比for i := 3; i < 7; i++ {...}更好
**
多行字符串用反引號(`)
**
用
_來跳過不用的參數(shù)
func f(a int, _ string) {}- ** 如果你要比較時間戳,請使用
time.Before或time.After,不要使用time.Sub來獲得 duration (持續(xù)時間),然后檢查它的值。 - ** 帶有上下文的函數(shù)第一個參數(shù)名為
ctx,形如:func foo(ctx Context, ...) - ** 幾個相同類型的參數(shù)定義可以用簡短的方式來進(jìn)行
func f(a int, b int, s string, p string)
func f(a, b int, s, p string)
** 一個 slice 的零值是 nil
var s []int fmt.Println(s, len(s), cap(s)) if s == nil { fmt.Println("nil!") } // Output: // [] 0 0 // nil!
var a []string
b := []string{}
fmt.Println(reflect.DeepEqual(a, []string{}))
fmt.Println(reflect.DeepEqual(b, []string{}))
// Output:
// false
// true** 不要將枚舉類型與
<,>,<=和>=進(jìn)行比較- 使用確定的值,不要像下面這樣做:
value := reflect.ValueOf(object)
kind := value.Kind()
if kind >= reflect.Chan && kind <= reflect.Slice {
// ...
}** 用
%+v來打印數(shù)據(jù)的比較全的信息** 注意空結(jié)構(gòu)
struct{}
func f1() {
var a, b struct{}
print(&a, "\n", &b, "\n") // Prints same address
fmt.Println(&a == &b) // Comparison returns false
}
func f2() {
var a, b struct{}
fmt.Printf("%p\n%p\n", &a, &b) // Again, same address
fmt.Println(&a == &b) // ...but the comparison returns true
}**
- 例如:
errors.Wrap(err, "additional message to a given error")
- 例如:
**
在 Go 里面要小心使用
range:for i := range aandfor i, v := range &a,都不是a的副本- 但是
for i, v := range a里面的就是a的副本
**
從 map 讀取一個不存在的 key 將不會 panic
value := map["no_key"]將得到一個 0 值value, ok := map["no_key"]更好
**
不要使用原始參數(shù)進(jìn)行文件操作
- 而不是一個八進(jìn)制參數(shù)
os.MkdirAll(root, 0700) - 使用此類型的預(yù)定義常量
os.FileMode
- 而不是一個八進(jìn)制參數(shù)
**
不要忘記為
iota指定一種類型
const (
_ = iota
testvar // testvar 將是 int 類型
)vs
type myType int
const (
_ myType = iota
testvar // testvar 將是 myType 類型
)不要在你不擁有的結(jié)構(gòu)上使用 encoding/gob
在某些時候,結(jié)構(gòu)可能會改變,而你可能會錯過這一點。因此,這可能會導(dǎo)致很難找到 bug。
不要依賴于計算順序,特別是在 return 語句中。
// BAD return res, json.Unmarshal(b, &res) // GOOD err := json.Unmarshal(b, &res) return res, err
防止結(jié)構(gòu)體字段用純值方式初始化,添加 _ struct {} 字段:
type Point struct {
X, Y float64
_ struct{} // to prevent unkeyed literals
}對于 Point {X:1,Y:1} 都可以,但是對于 Point {1,1} 則會出現(xiàn)編譯錯誤:
./file.go:1:11: too few values in Point literal
當(dāng)在你所有的結(jié)構(gòu)體中添加了 _ struct{} 后,使用 go vet 命令進(jìn)行檢查,(原來聲明的方式)就會提示沒有足夠的參數(shù)。
為了防止結(jié)構(gòu)比較,添加 func 類型的空字段
type Point struct {
_ [0]func() // unexported, zero-width non-comparable field
X, Y float64
}http.HandlerFunc 比 http.Handler 更好
用 http.HandlerFunc 你僅需要一個 func,http.Handler 需要一個類型。
移動 defer 到頂部
這可以提高代碼可讀性并明確函數(shù)結(jié)束時調(diào)用了什么。
JavaScript 解析整數(shù)為浮點數(shù)并且你的 int64 可能溢出
用 json:"id,string" 代替
type Request struct {
ID int64 `json:"id,string"`
}并發(fā)
** 以線程安全的方式創(chuàng)建單例(只創(chuàng)建一次)的最好選擇是
sync.Once- 不要用 flags, mutexes, channels or atomics
** 永遠(yuǎn)不要使用
select{}, 省略通道, 等待信號** 不要關(guān)閉一個發(fā)送(寫入)管道,應(yīng)該由創(chuàng)建者關(guān)閉
- 往一個關(guān)閉的 channel 寫數(shù)據(jù)會引起 panic
**
math/rand中的func NewSource(seed int64) Source不是并發(fā)安全的,默認(rèn)的lockedSource是并發(fā)安全的。** 當(dāng)你需要一個自定義類型的 atomic 值時,可以使用 atomic.Value
性能
** 不要省略
defer- 在大多數(shù)情況下 200ns 加速可以忽略不計
** 總是關(guān)閉 http body
defer r.Body.Close()- 除非你需要泄露 goroutine
** 過濾但不分配新內(nèi)存
b := a[:0]
for _, x := range a {
if f(x) {
b = append(b, x)
}
}為了幫助編譯器刪除綁定檢查,請參見此模式 _ = b [7]
**
time.Time有指針字段time.Location并且這對 go GC 不好- 只有使用了大量的
time.Time才(對性能)有意義,否則用 timestamp 代替
- 只有使用了大量的
**
regexp.MustCompile比regexp.Compile更好- 在大多數(shù)情況下,你的正則表達(dá)式是不可變的,所以你最好在
func init中初始化它
- 在大多數(shù)情況下,你的正則表達(dá)式是不可變的,所以你最好在
** 請勿在你的熱點代碼中過度使用
fmt.Sprintf. 由于維護(hù)接口的緩沖池和動態(tài)調(diào)度,它是很昂貴的。- 如果你正在使用
fmt.Sprintf("%s%s", var1, var2), 考慮使用簡單的字符串連接。 - 如果你正在使用
fmt.Sprintf("%x", var), 考慮使用hex.EncodeToStringorstrconv.FormatInt(var, 16)
- 如果你正在使用
** 如果你不需要用它,可以考慮丟棄它,例如
io.Copy(ioutil.Discard, resp.Body)- HTTP 客戶端的傳輸不會重用連接,直到body被讀完和關(guān)閉。
res, _ := client.Do(req) io.Copy(ioutil.Discard, res.Body) defer res.Body.Close()
** 不要在循環(huán)中使用 defer,否則會導(dǎo)致內(nèi)存泄露
- 因為這些 defer 會不斷地填滿你的棧(內(nèi)存)
** 不要忘記停止 ticker, 除非你需要泄露 channel
ticker := time.NewTicker(1 * time.Second) defer ticker.Stop()
** 用自定義的 marshaler 去加速 marshaler 過程
- 但是在使用它之前要進(jìn)行定制!
func (entry Entry) MarshalJSON() ([]byte, error) {
buffer := bytes.NewBufferString("{")
first := true
for key, value := range entry {
jsonValue, err := json.Marshal(value)
if err != nil {
return nil, err
}
if !first {
buffer.WriteString(",")
}
first = false
buffer.WriteString(key + ":" + string(jsonValue))
}
buffer.WriteString("}")
return buffer.Bytes(), nil
}**
sync.Map不是萬能的,沒有很強(qiáng)的理由就不要使用它。**
在
sync.Pool中分配內(nèi)存存儲非指針數(shù)據(jù)**
為了隱藏逃生分析的指針,你可以小心使用這個函數(shù)::
// noescape hides a pointer from escape analysis. noescape is
// the identity function but escape analysis doesn't think the
// output depends on the input. noescape is inlined and currently
// compiles down to zero instructions.
//go:nosplit
func noescape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
return unsafe.Pointer(x ^ 0)
}**
對于最快的原子交換,你可以使用這個
m := (*map[int]int)(atomic.LoadPointer(&ptr))**
如果執(zhí)行許多順序讀取或?qū)懭氩僮?,請使用緩沖 I/O
- 減少系統(tǒng)調(diào)用次數(shù)
**
有 2 種方法清空一個 map:
- 重用 map 內(nèi)存 (但是也要注意 m 的回收)
for k := range m {
delete(m, k)
}- 分配新的
m = make(map[int]int)
構(gòu)建
** 用這個命令
go build -ldflags="-s -w" ...去掉你的二進(jìn)制文件** 拆分構(gòu)建不同版本的簡單方法
- 用
// +build integration并且運行他們go test -v --tags integration .
- 用
** 最小的 Go Docker 鏡像
- twitter.com/bbrodriges/…
CGO_ENABLED=0 go build -ldflags="-s -w" app.go && tar C app | docker import - myimage:latest
** run go format on CI and compare diff
- 這將確保一切都是生成的和承諾的
** 用最新的 Go 運行 Travis-CI,用
travis 1** 檢查代碼格式是否有錯誤
diff -u <(echo -n) <(gofmt -d .)
測試
- ** 測試名稱
package_test比package要好 - **
go test -short允許減少要運行的測試數(shù)
func TestSomething(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
}- ** 根據(jù)系統(tǒng)架構(gòu)跳過測試
if runtime.GOARM == "arm" {
t.Skip("this doesn't work under ARM")
}** 用
testing.AllocsPerRun跟蹤你的內(nèi)存分配** 多次運行你的基準(zhǔn)測試可以避免噪音。
go test -test.bench=. -count=20
工具
**
快速替換
gofmt -w -l -r "panic(err) -> log.Error(err)" .**
go list允許找到所有直接和傳遞的依賴關(guān)系go list -f '{{ .Imports }}' packagego list -f '{{ .Deps }}' package
**
對于快速基準(zhǔn)比較,我們有一個
benchstat工具。**
go-critic linter 從這個文件中強(qiáng)制執(zhí)行幾條建議
**
go mod why -m <module>告訴我們?yōu)槭裁刺囟ǖ哪K在go.mod文件中。**
GOGC=off go build ...應(yīng)該會加快構(gòu)建速度 source**
內(nèi)存分析器每 512KB 記錄一次分配。你能通過
GODEBUG環(huán)境變量增加比例,來查看你的文件的更多詳細(xì)信息。**
go mod why -m <module>告訴我們?yōu)槭裁刺囟ǖ哪K是在go.mod文件中。
其他
- ** dump goroutines
go func() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGQUIT)
buf := make([]byte, 1<<20)
for {
<-sigs
stacklen := runtime.Stack(buf, true)
log.Printf("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n" , buf[:stacklen])
}
}()** 在編譯期檢查接口的實現(xiàn)
var _ io.Reader = (*MyFastReader)(nil)
** len(nil) = 0
** 匿名結(jié)構(gòu)很酷
var hits struct {
sync.Mutex
n int
}
hits.Lock()
hits.n++
hits.Unlock()**
httputil.DumpRequest是非常有用的東西,不要自己創(chuàng)建**
獲得調(diào)用堆棧,我們可以使用
runtime.Caller**
要 marshal 任意的 JSON, 你可以 marshal 為
map[string]interface{}{}**
配置你的
CDPATH以便你能在任何目錄執(zhí)行cd github.com/golang/go- 添加這一行代碼到
bashrc(或者其他類似的)export CDPATH=$CDPATH:$GOPATH/src
- 添加這一行代碼到
**
從一個 slice 生成簡單的隨機(jī)元素
[]string{"one", "two", "three"}[rand.Intn(3)]
參考資料: github.com/cristaloleg…
總結(jié)
到此這篇關(guān)于Go你不得不知道的一些實用小技巧的文章就介紹到這了,更多相關(guān)Go小技巧內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文帶你了解Go語言標(biāo)準(zhǔn)庫math和rand的常用函數(shù)
這篇文章主要為大家詳細(xì)介紹了Go語言標(biāo)準(zhǔn)庫math和rand中的常用函數(shù),文中的示例代碼講解詳細(xì), 對我們學(xué)習(xí)Go語言有一定的幫助,感興趣的小伙伴可以了解一下2022-12-12
Go和RabbitMQ構(gòu)建高效的消息隊列系統(tǒng)
本文主要介紹了使用Go語言和RabbitMQ搭建一個簡單的消息隊列系統(tǒng),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-01-01
Jaeger?Client?Go入門并實現(xiàn)鏈路追蹤
這篇文章介紹了Jaeger?Client?Go入門并實現(xiàn)鏈路追蹤的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03
利用Go實現(xiàn)一個簡易DAG服務(wù)的示例代碼
DAG的全稱是Directed Acyclic Graph,即有向無環(huán)圖,DAG廣泛應(yīng)用于表示具有方向性依賴關(guān)系的數(shù)據(jù),如任務(wù)調(diào)度、數(shù)據(jù)處理流程、項目管理以及許多其他領(lǐng)域,下面,我將用Go語言示范如何實現(xiàn)一個簡單的DAG服務(wù),需要的朋友可以參考下2024-03-03
詳解Go語言中如何通過Goroutine實現(xiàn)高并發(fā)
在Go語言中,并發(fā)編程是一個核心且強(qiáng)大的特性,Go語言通過goroutine和channel等機(jī)制,使得并發(fā)編程變得更加簡單和直觀,本文給大家介紹了Go語言中如何通過Goroutine快速實現(xiàn)高并發(fā),感興趣的小伙伴跟著小編一起來看看吧2024-10-10

