高質(zhì)量編程:
什么是高質(zhì)量:
? ——編寫代碼能達到正確可靠,簡潔清晰的目標
各種邊界條件是否考慮完備
異常情況處理,穩(wěn)定性保證
易讀易維護
編程原則
簡單性
消除“多余的復雜性”,以簡單清晰的邏輯編寫代碼
不理解的代碼無法修復改進
可讀性
代碼是寫給人看的,而不是機器
編寫可維護代碼的第一步是確保代碼可讀
生產(chǎn)力
團隊整體工作效率非常重要
編寫規(guī)范
-
代碼格式
gofmt: go語言官方提供工具,自動格式化為官方統(tǒng)一風格
goimports:go語言官方提供工具,實際等于gofmt加上依賴包管理,自動增刪依賴包引用,對其排序分類等
-
注釋
注釋應該做的:
應該解釋代碼作用
應該解釋代碼如何做的
應該解釋代碼實現(xiàn)的原因
應該解釋代碼什么情況會出錯
應該解釋公共符號(公共的變量,常量,函數(shù),不應該取決與代碼的長度來選擇注釋)
代碼是最好的注釋,并且注釋應該要提供代碼未表達出的上下文信息
-
命名規(guī)范
簡介勝于冗長
for index := 0; index < len(s); index++{//bad //do something } for i := 0; i < len(s); i++{ //good //do something }
縮略詞全大寫,但當其位于變量開頭且不需要導出時,使用全小寫
例如:使用ServeHTTP而不是ServeHttp 使用XMLHTTPRequest或者xmlHTTPRequest
變量距離其被使用的地方越遠,越需要攜帶更多的上下文信息
函數(shù)名不攜帶包名的上下文信息,因為包名和函數(shù)總是成對出現(xiàn)的
函數(shù)名盡量簡短
對于package來說,只由小寫字母組成,不包含大寫字母和下劃線
簡短并包含一定上下文信息,例如schema,task
不要與標準庫同名
不使用常用變量名作為包名,例如使用bufo而不是buf
使用單數(shù)不使用復數(shù),例如使用encoding而不是encodings
謹慎使用縮寫
-
控制流程
避免嵌套
if foo{ // bad return x }else{ return nil } if foo{ // good return x } return nil
簡單來說差不多少用else -f else的這樣以后添加修改方便
處理邏輯盡量走直線,避免復雜嵌套分支
-
錯誤和異常處理
簡單錯誤:僅出現(xiàn)一次的錯誤,有限使用errors.New來創(chuàng)建匿名變量
func ErrorsNew(size int) error { if size > 10 { return errors.New("size bigger , should be less then 10") } return nil }
錯誤的Wrap和Unwrap
在fmt.Errorf中使用%w關(guān)鍵字將一個錯誤關(guān)聯(lián)到錯誤鏈中
list, _ , err := c.GetBytes(cache.Subkey(a.actionID , "srcfiles")) if err != nil{ return fmt.Errorf("reading srcfiles list : %w" , err) }
錯誤判定
判定一個錯誤是否為特定錯誤,使用errors.ls
不同于使用==,使用該方法可判定錯誤鏈上的所有錯誤是否含有特定錯誤
data, err = lockedfile.Read( targ ) if errors.Is(err , fs.ErrNotExist){ // Treat non-existent as empty, to bootstrap //the "latest" filethe first time we connect to a given database. return []byteP{} , nil } return data, err }
在錯誤鏈上過去特定錯誤使用errors.AS
if _ ,err := os.Open("non-exit"); err != nil{ var pathError *fs.PathError if errors.As(err , &pathError){ fmt.Println("Failed at path :" , pathError.Path) }else{ fmt.Println(err) } }
panic
不建議在業(yè)務代碼中使用panic
調(diào)用函數(shù)不包含recover會造成程序崩潰
若問題可以被屏蔽或解決,建議使用error代替panic
recover
recover只能被defer的函數(shù)中使用
嵌套無法生效
只在當前goroutine生效
defer語句是后進先出
優(yōu)化與性能測試:
性能優(yōu)化的前提是滿足正確可靠,簡潔清晰等質(zhì)量因素
性能優(yōu)化是綜合評估,有時候時間效率和空間效率可能對立
Benchmark
go語言提供支持基準性能測試的工具
func BenchmarkFib10(b *testing.B) {
for i := 0; i < 10; i++{
Fib(10)
}
}
func Fib(n int) int {
if n < 2{
return n
}
return Fib(n - 1) + Fib(n - 2)
}
go test -bench=. -benchmem // 執(zhí)行命令
上面第一行-16差不多是cpu核數(shù)了
10000… 是執(zhí)行次數(shù)
0.0005517 是每次花費時間
0B 是每次申請內(nèi)存
0allocs是每次申請幾次內(nèi)存
性能優(yōu)化建議Slice
預分配
盡可能在使用make初始化切片時提供容量信息
func BenchmarkNoPreAlloc(){
data := make([]int , 0)
for k := 0; k < n; k++{
data = append(data, k)
}
}
func BenchmarkPreAlloc(){
data := make([]int , n)
for k := 0; k < n; k++{
data = append(data, k )
}
}
大內(nèi)存未釋放
在已有的切片基礎(chǔ)上創(chuàng)建切片,不會創(chuàng)建新的底層數(shù)組,如下
func Copy(origin []int) []int{
return origin[3:len(origin)]
}
上述就是想創(chuàng)建個新切片,但是其實底層數(shù)組用的是同一個,如果說傳入?yún)?shù)在之后不用的,但是因為有個這個返回值只用一段數(shù)據(jù)卻占用這之前的一大塊數(shù)據(jù),內(nèi)存就浪費了
另一個問題就是由于工用一個數(shù)組,你改變一個切片的值,另一個也就改變了
func main() {
a := []int{1, 2, 3, 4, 5}
b := a
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(b) //[1 2 3 4 5]
b[0] = 1000
fmt.Println(a) //[1000 2 3 4 5]
fmt.Println(b) //[1000 2 3 4 5]
}
由于切片是引用類型,所以a和b其實都指向了同一塊內(nèi)存地址。修改b的同時a的值也會發(fā)生變化。
Go語言內(nèi)建的copy()
函數(shù)可以迅速地將一個切片的數(shù)據(jù)復制到另外一個切片空間中,copy()
函數(shù)的使用格式如下:
copy(destSlice, srcSlice []T)
其中:
- srcSlice: 數(shù)據(jù)來源切片
- destSlice: 目標切片
舉個例子:
func main() {
// copy()復制切片
a := []int{1, 2, 3, 4, 5}
c := make([]int, 5, 5)
copy(c, a) //使用copy()函數(shù)將切片a中的元素復制到切片c
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1 2 3 4 5]
c[0] = 1000
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1000 2 3 4 5]
}
性能優(yōu)化建議-Map
const n = 10000000
func BenchmarkNoPreAlloc(b *testing.B) {
data := make(map[int]int , 0)
for k := 0; k < n; k++ {
data[k] = 1
}
}
func BenchmarkPreAlloc(b *testing.B) {
data := make(map[int]int , n)
for k := 0; k < n; k++ {
data[k] = 1
}
}
可以看出,結(jié)果和切片差不多
分析:
? 不斷向map中添加元素操作會觸發(fā)map擴容
? 提前分配好空間可以減少內(nèi)存拷貝和ReHash的消耗
性能優(yōu)化建議-字符串處理
常見的字符串拼接方式
func plus(n int , str string) string{
s := ""
for i := 0; i < n; i++{
s += str
}
return s
}
func StrBulider(n int , str string)string{
var builder strings.Builder
for i := 0; i < n; i++{
builder.WriteString(str)
}
return builder.String()
}
func ByteBuffer(n int , str string)string{
buf := new(bytes.Buffer)
for i := 0; i < n; i++{
buf.WriteString(str)
}
return buf.String()
}
使用 + 拼接性能最差,后面?zhèn)z個差不多,strings.Buffer更快
分析:
? 字符串在Go語言中是不可變類型,占用內(nèi)存大小固定
? 使用 + 每次都會重新分配內(nèi)存文章來源:http://www.zghlxwxcb.cn/news/detail-617035.html
? 后面的底層都是 []byte數(shù)組,內(nèi)存擴容策略,不需要每次拼接重新分配內(nèi)存文章來源地址http://www.zghlxwxcb.cn/news/detail-617035.html
到了這里,關(guān)于Go-高質(zhì)量編程與性能調(diào)優(yōu)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!