高質量編程簡介及編碼規範 高質量: 各種邊界條件考慮完備 異常情況處理,穩定性 易讀易維護 編程原則 簡單性 可讀性 生產力 編碼規範 公共符號始終要註釋 例外:實現介面的方法不需要註釋 格式化 使用gofmt(官方工具)自動格式化 註釋 代碼作用(適合公共符號) 代碼如何實現 (適合註釋實現過程) ...
高質量編程簡介及編碼規範
高質量:
-
各種邊界條件考慮完備
-
異常情況處理,穩定性
-
易讀易維護
編程原則
-
簡單性
-
可讀性
-
生產力
公共符號始終要註釋
例外:實現介面的方法不需要註釋
格式化
使用gofmt(官方工具)自動格式化
註釋
-
代碼作用(適合公共符號)
-
代碼如何實現 (適合註釋實現過程)
-
代碼實現的原因(適合解釋代碼的外部因素和提供額外的上下文)
-
代碼什麼情況下出錯(適合代碼的限制條件)
公共符號始終要註釋·包中聲明的每個公共的符號: 變數、常量、函數以及結構都需要添加註釋 .任何既不明顯也不簡短的公 共功能必須予以註釋 無論長度或複雜程度如何, 對庫中的任何函數都必須進行註釋
命名規範
變數
縮略詞全大寫,但當其位於變數開頭且不需要導出時,使用全小寫
-
例如使用ServeHTTP而不是ServeHttp
-
使用XMLHTTPRequest或者xmlHTTPRequest
-
變數距離其被使用的地方越遠,則需要攜帶越多的上下文信息
-
全局變數在其名字中需要更多的上下文信息,使得在不同地方可以輕易辨認出其含義
函數
函數名不攜帶包名的上下文信息,因為包名和函數名總是成對出現的·函數名儘量簡短 當名為foo的包某個函數返回類型Foo時,可以省略類型信息而不導致歧義 當名為foo的包某個函數返回類型T時(T並不是Foo),可以在函數名中加入類型信息
package
-
只由小寫字母組成。不包含大寫字母和下劃線等字元·
-
簡短並包含一定的上下文信息。例如schema、task 等·
-
不要與標準庫同名。例如不要使用sync或者strings
以下規則儘量滿足,以標準庫包名為例
-
不使用常用變數名作為包名。例如使用bufio而不是buf·使用單數而不是複數。例如使用encoding而不是encodings
-
謹慎地使用縮寫。例如使用fmt 在不破壞上下文的情況下比 format 更加簡短
控制流程
避免嵌套
儘量保存為最小縮進
錯誤處理
簡單錯誤
-
簡單的錯誤指的是僅出現一次的錯誤,且在其他地方不需要捕獲該錯誤
-
優先使用errors.New來創建匿名變數來直接表示簡單錯誤
-
如果有格式化的需求,使用fmt.Errorf
錯誤的Wrap和 Unwrap
·錯誤的Wrap 實際上是提供了一個error嵌套另一個error的能力,從而生成一個error的跟蹤鏈 ·在 fmt.Errorf中使用:%w關鍵字來將一個錯誤關聯至 錯誤鏈中
錯誤判定
-
判定一個錯誤是否為特定錯誤,使用errors.Is
-
不同於使用==,使用該方法可以判定錯誤鏈上的所有錯誤是否含有特定的錯誤
panic
-
不建議在業務代碼中使用panic
-
·調用函數不包含recover會造成程式崩潰·若問題可以被屏蔽或解決,建議使用error代替panic
-
當程式啟動階段發生不可逆轉的錯誤時,可以在init 或 main函數中使用panic
性能優化
benchmark工具
slice
提前指定大小
在大切片上創建小切片,使用copy代替
string
使用strings.builder 和java類似
空結構體
使用空結構體struct{}實列不占用空間
map
map 預分配記憶體分析
-
不斷向map中添加元素的操作會觸發map的擴容·
-
提前分配好空間可以減少記憶體拷貝和Rehash 的消耗·
-
建議根據實際需求提前預估好需要的空間
使用atomic 包
鎖的實現是通過操作系統來實現,屬於系統調用.atomic 操作是通過硬體實現,
效率比鎖高sync.Mutex應該用來保護一段邏輯,不僅僅用於保護一個變數。
對於非數值操作,可以使用atomic.Value,能承載一個interface}
實戰
直接拉取倉庫
分析的博客:
性能分析工具 pprof
項目目錄
沒有外部依賴,直接運行即可
保持程式運行,打開瀏覽器訪問 http://localhost:6060/debug/pprof/
,可以看到如下頁面:
頁面上展示了可用的程式運行採樣數據,分別有:
類型 | 描述 | 備註 |
---|---|---|
allocs | 記憶體分配情況的採樣信息 | 可以用瀏覽器打開,但可讀性不高 |
blocks | 阻塞操作情況的採樣信息 | 可以用瀏覽器打開,但可讀性不高 |
cmdline | 顯示程式啟動命令及參數 | 可以用瀏覽器打開,這裡會顯示 ./go-pprof-practice |
goroutine | 當前所有協程的堆棧信息 | 可以用瀏覽器打開,但可讀性不高 |
heap | 堆上記憶體使用情況的採樣信息 | 可以用瀏覽器打開,但可讀性不高 |
mutex | 鎖爭用情況的採樣信息 | 可以用瀏覽器打開,但可讀性不高 |
profile | CPU 占用情況的採樣信息 | 瀏覽器打開會下載文件 |
threadcreate | 系統線程創建情況的採樣信息 | 可以用瀏覽器打開,但可讀性不高 |
trace | 程式運行跟蹤信息 | 瀏覽器打開會下載文件,本文不涉及,可另行參閱 |
命令行
go tool pprof http://localhost:6060/debug/pprof/profile
topN 查看占用最多的函數
(pprof) top
Showing nodes accounting for 8.91s, 98.67% of 9.03s total
Dropped 35 nodes (cum <= 0.05s)
flat flat% sum% cum cum%
8.91s 98.67% 98.67% 8.93s 98.89% github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat
0 0% 98.67% 8.93s 98.89% github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Live
0 0% 98.67% 8.97s 99.34% main.main
0 0% 98.67% 8.97s 99.34% runtime.main
0 0% 98.67% 0.05s 0.55% runtime.systemstack
flat=Cum 函數中沒有調用其他函數
flat=0 函數中只有其他函數的調用
輸入 list Eat
,查看問題具體在代碼的哪一個位置:根據指定的正則表達式查找
(pprof) list Eat
Total: 9.03s
ROUTINE ======================== github.com/wolfogre/go-pprof-practice/animal/canidae/dog.(*Dog).Eat in H:\go-pprof-practice\animal\canidae\dog\dog.go
0 10ms (flat, cum) 0.11% of Total
. . 26: d.Pee()
. . 27: d.Run()
. . 28: d.Howl()
. . 29:}
. . 30:func (d *Dog) Eat() {
. 10ms 31: log.Println(d.Name(), "eat")
. . 32:}
. . 33:
. . 34:func (d *Dog) Drink() {
. . 35: log.Println(d.Name(), "drink")
. . 36:}
ROUTINE ======================== github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat in H:\go-pprof-practice\animal\felidae\tiger\tiger.go
8.91s 8.93s (flat, cum) 98.89% of Total
. . 26:無效的迴圈
. . 27:*/
. . 28:func (t *Tiger) Eat() {
. . 29: log.Println(t.Name(), "eat")
. . 30: loop := 10000000000
8.91s 8.93s 31: for i := 0; i < loop; i++ {
. . 32: // do nothing
. . 33: }
. . 34:}
. . 35:
. . 36:func (t *Tiger) Drink() {
web 調用關係可視化
可以訪問
調查記憶體
go tool pprof http://localhost:6060/debug/pprof/heap
top
(pprof) top
Showing nodes accounting for 1.20GB, 100% of 1.20GB total
Dropped 4 nodes (cum <= 0.01GB)
flat flat% sum% cum cum%
1.20GB 100% 100% 1.20GB 100% github.com/wolfogre/go-pprof-practice/animal/muridae/mouse.(*Mouse).Steal
0 0% 100% 1.20GB 100% github.com/wolfogre/go-pprof-practice/animal/muridae/mouse.(*Mouse).Live
0 0% 100% 1.20GB 100% main.main
0 0% 100% 1.20GB 100% runtime.main
查看到占用1G多的記憶體
記憶體回收
go tool pprof http://localhost:6060/debug/pprof/allocs
top
(pprof) top
Showing nodes accounting for 592MB, 99.75% of 593.50MB total
Dropped 16 nodes (cum <= 2.97MB)
flat flat% sum% cum cum%
592MB 99.75% 99.75% 592MB 99.75% github.com/wolfogre/go-pprof-practice/animal/canidae/dog.(*Dog).Run (inline)
0 0% 99.75% 592MB 99.75% github.com/wolfogre/go-pprof-practice/animal/canidae/dog.(*Dog).Live
0 0% 99.75% 592MB 99.75% main.main
0 0% 99.75% 592.50MB 99.83% runtime.main
可以看到 github.com/wolfogre/go-pprof-practice/animal/canidae/dog.(*Dog).Run
會進行無意義的記憶體申請,而這個函數又會被頻繁調用,這才導致程式不停地進行 GC:
func (d *Dog) Run() {
log.Println(d.Name(), "run")
_ = make([]byte, 16 * constant.Mi)
}
排查協程泄露
go tool pprof http://localhost:6060/debug/pprof/goroutine
(pprof) top
Showing nodes accounting for 103, 99.04% of 104 total
Showing top 10 nodes out of 33
flat flat% sum% cum cum%
102 98.08% 98.08% 102 98.08% runtime.gopark
1 0.96% 99.04% 1 0.96% runtime.goroutineProfileWithLabels
0 0% 99.04% 100 96.15% github.com/wolfogre/go-pprof-practice/animal/canidae/wolf.(*Wolf).Drink.func1
0 0% 99.04% 1 0.96% github.com/wolfogre/go-pprof-practice/animal/felidae/cat.(*Cat).Live
0 0% 99.04% 1 0.96% github.com/wolfogre/go-pprof-practice/animal/felidae/cat.(*Cat).Pee
0 0% 99.04% 1 0.96% internal/poll.(*FD).Accept
0 0% 99.04% 1 0.96% internal/poll.(*FD).acceptOne
0 0% 99.04% 1 0.96% internal/poll.(*pollDesc).wait
0 0% 99.04% 1 0.96% internal/poll.execIO
排查鎖的爭用
go tool pprof http://localhost:6060/debug/pprof/mutex
(pprof) top
Showing nodes accounting for 126.40s, 100% of 126.40s total
flat flat% sum% cum cum%
126.40s 100% 100% 126.40s 100% sync.(*Mutex).Unlock (inline)
0 0% 100% 126.40s 100% github.com/wolfogre/go-pprof-practice/animal/canidae/wolf.(*Wolf).Howl.func1
func (w *Wolf) Howl() {
log.Println(w.Name(), "howl")
m := &sync.Mutex{}
m.Lock()
go func() {
time.Sleep(time.Second)
m.Unlock()
}()
m.Lock()
}