關(guān)于Go你不得不知道的一些實(shí)用小技巧
Go 箴言
- 不要通過共享內(nèi)存進(jìn)行通信,通過通信共享內(nèi)存
- 并發(fā)不是并行
- 管道用于協(xié)調(diào);互斥量(鎖)用于同步
- 接口越大,抽象就越弱
- 利用好零值
- 空接口
interface{}
沒有任何類型約束 - Gofmt 的風(fēng)格不是人們最喜歡的,但 gofmt 是每個人的最愛
- 允許一點(diǎn)點(diǎn)重復(fù)比引入一點(diǎn)點(diǎn)依賴更好
- 系統(tǒng)調(diào)用必須始終使用構(gòu)建標(biāo)記進(jìn)行保護(hù)
- 必須始終使用構(gòu)建標(biāo)記保護(hù) Cgo
- Cgo 不是 Go
- 使用標(biāo)準(zhǔn)庫的
unsafe
包,不能保證能如期運(yùn)行 - 清晰比聰明更好
- 反射永遠(yuǎn)不清晰
- 錯誤是值
- 不要只檢查錯誤,還要優(yōu)雅地處理它們
- 設(shè)計(jì)架構(gòu),命名組件,(文檔)記錄細(xì)節(jié)
- 文檔是供用戶使用的
- 不要(在生產(chǎn)環(huán)境)使用
panic()
Go 之禪
- 每個 package 實(shí)現(xiàn)單一的目的
- 顯式處理錯誤
- 盡早返回,而不是使用深嵌套
- 讓調(diào)用者處理并發(fā)(帶來的問題)
- 在啟動一個 goroutine 時(shí),需要知道何時(shí)它會停止
- 避免 package 級別的狀態(tài)
- 簡單很重要
- 編寫測試以鎖定 package API 的行為
- 如果你覺得慢,先編寫 benchmark 來證明
- 適度是一種美德
- 可維護(hù)性
代碼
使用 go fmt 格式化
讓團(tuán)隊(duì)一起使用官方的 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
的定義時(shí),有時(shí)不容易理解如何使用該值,例如:
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)該是可取消的或者至少是可超時(shí)的
** 為整型常量值實(shí)現(xiàn)
Stringer
接口** 檢查
defer
中的錯誤
defer func() { err := ocp.Close() if err != nil { rerr = err } }()
** 不要在
checkErr
函數(shù)中使用panic()
或os.Exit()
** 僅僅在很特殊情況下才使用 panic, 你必須要去處理 error
** 不要給枚舉使用別名,因?yàn)檫@打破了類型安全
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) {}
- ** 如果你要比較時(shí)間戳,請使用
time.Before
或time.After
,不要使用time.Sub
來獲得 duration (持續(xù)時(shí)間),然后檢查它的值。 - ** 帶有上下文的函數(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 a
andfor 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
在某些時(shí)候,結(jié)構(gòu)可能會改變,而你可能會錯過這一點(diǎn)。因此,這可能會導(dǎo)致很難找到 bug。
不要依賴于計(jì)算順序,特別是在 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é)束時(shí)調(diào)用了什么。
JavaScript 解析整數(shù)為浮點(diǎn)數(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 值時(shí),可以使用 atomic.Value
性能
** 不要省略
defer
- 在大多數(shù)情況下 200ns 加速可以忽略不計(jì)
** 總是關(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á)式是不可變的,所以你最好在
** 請勿在你的熱點(diǎn)代碼中過度使用
fmt.Sprintf
. 由于維護(hù)接口的緩沖池和動態(tài)調(diào)度,它是很昂貴的。- 如果你正在使用
fmt.Sprintf("%s%s", var1, var2)
, 考慮使用簡單的字符串連接。 - 如果你正在使用
fmt.Sprintf("%x", var)
, 考慮使用hex.EncodeToString
orstrconv.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)存泄露
- 因?yàn)檫@些 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
并且運(yùn)行他們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 運(yùn)行 Travis-CI,用
travis 1
** 檢查代碼格式是否有錯誤
diff -u <(echo -n) <(gofmt -d .)
測試
- ** 測試名稱
package_test
比package
要好 - **
go test -short
允許減少要運(yùn)行的測試數(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)存分配** 多次運(yùn)行你的基準(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 }}' package
go 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]) } }()
** 在編譯期檢查接口的實(shí)現(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你不得不知道的一些實(shí)用小技巧的文章就介紹到這了,更多相關(guān)Go小技巧內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在 Golang 中實(shí)現(xiàn) Cache::remember 方法詳解
這篇文章主要介紹了在 Golang 中實(shí)現(xiàn) Cache::remember 方法詳解,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03Golang 基礎(chǔ)之函數(shù)使用(匿名遞歸閉包)實(shí)例詳解
這篇文章主要為大家介紹了Golang 基礎(chǔ)之函數(shù)使用(匿名遞歸閉包)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10