高質量編程簡介及編碼規范
高質量:
-
各種邊界條件考慮完備
-
例外情況處理,穩定性
-
易讀易維護
編程原則
-
簡單性
-
可讀性
-
生產力
編碼規范
公共符號始終要注釋
例外:實作介面的方法不需要注釋
格式化
使用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}
實戰
直接拉取倉庫
wolfogre/go-pprof-practice: go pprof practice. (github.com)
分析的博客:
golang pprof 實戰 | Wolfogre's Blog
性能分析工具 pprof
專案目錄
沒有外部依賴,直接運行即可
保持程式運行,打開瀏覽器訪問 http://localhost:6060/debug/pprof/,可以看到如下頁面:
頁面上展示了可用的程式運行采樣資料,分別有:
| 型別 | 描述 | 備注 |
|---|---|---|
| allocs | 記憶體分配情況的采樣資訊 | 可以用瀏覽器打開,但可讀性不高 |
| blocks | 阻塞操作情況的采樣資訊 | 可以用瀏覽器打開,但可讀性不高 |
| cmdline | 顯示程式啟動命令及引數 | 可以用瀏覽器打開,這里會顯示 ./go-pprof-practice |
| goroutine | 當前所有協程的堆疊資訊 | 可以用瀏覽器打開,但可讀性不高 |
| heap | 堆上記憶體使用情況的采樣資訊 | 可以用瀏覽器打開,但可讀性不高 |
| mutex | 鎖爭用情況的采樣資訊 | 可以用瀏覽器打開,但可讀性不高 |
| profile | CPU 占用情況的采樣資訊 | 瀏覽器打開會下載檔案 |
| threadcreate | 系統執行緒創建情況的采樣資訊 | 可以用瀏覽器打開,但可讀性不高 |
| trace | 程式運行跟蹤資訊 | 瀏覽器打開會下載檔案,本文不涉及,可另行參閱《深入淺出 Go 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 呼叫關系可視化
可以訪問 graphviz 官網尋找適合自己作業系統的安裝方法
調查記憶體
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()
}
可以看到,這個鎖由主協程 Lock,并啟動子協程去 Unlock,主協程會阻塞在第二次 Lock 這兒等待子協程完成任務,但由于子協程足足睡眠了一秒,導致主協程等待這個鎖釋放足足等了一秒鐘,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/550915.html
標籤:其他
上一篇:CAS的service引數驗證
下一篇:返回列表
