这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
一、高质量编程
什么是高质量?
- 各种边界条件是否考虑完备
- 异常情况处理,稳定性保证
- 易读易维护
编码规范
1.代码格式
推荐使用gofmt自动格式化代码,gofmt能够自动格式化go语言代码为官方统一风格。
2.注释---写好代码是前提
注释应该写什么?
- 注释应该解释代码作用
- 注释应该解释代码如何做的
- 注释应该解释代码实现的原因
- 注释应该解释代码什么情况会出错
包中声明的每个公共符号,包括变量、常量、函数以及结构都需要添加注释。
3.命名规范---最容易且必须做到
变量命名规则
第一个单词小写,后面驼峰
ex:
var userDao UserDao
- 简洁胜于冗长
缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写
- 例如使用ServeHTTP而不是ServeHttp
- 使用XMLHTTPRequest或者xmlHTTPRequest
- 变量距离其被使用的地方越远,则需要携带越多的上下文信息,即越详细
- 全局变量在其名字中需要更多的上下文信息,使得在不同地方可以轻易辨认出其含义
函数命名规则
一般为驼峰式(非导出函数例外)
ex:
func QueryUserById()
- 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的
- 函数名尽量简短
- 当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义
- 当名为foo的包某个函数返回类型T时(T 并不是Foo),可以在函数名中加入类型信息
包命名规则
全小写!!!
- 只由小写字母组成。不包含大写字母和下划线等字符
- 简短并包含一定的上下文信息。例如schema、task 等
- 不要与标准库同名。例如不要使用syne或者strings
结构体命名规则
名字和字段都采用驼峰式,非导出除外
type User struct {
Id int64
Name string
Age int64
CreateTime time.Time
}
4.控制流程
避免嵌套(能少写代码就少写)
if condition{ retrun x } return y
优先处理异常状况
if err!=nil{ }
5.错误和异常处理
- 少用
panic()
,除非真正异常的情况 - 多用
errors.New()
和fmt.Errof()
错误链操作
- 判断一个错误是否为特定错误
errors.Is(errs,targetError)
- 从错误链上获取特定错误
errors.As(errs,&targetError)
二、性能优化
1.性能优化指南
如何评价?
举个栗子~
//基准测试函数
func BenchmarkFab10(b *testing.B) {
for i := 0; i < b.N; i++ {
//计算斐波拉契数列
Fab(10)
}
}
执行命令go test -bench="." -benchmem
特别注意引号
-bench
参数支持传入一个正则表达式,匹配到的用例才会得到执行
依次表示:
- GOMAXPROCS 默认为CPU核数
- 即b.N的值
- 每次执行花费的时间
- 每次执行申请多大的内存
- 每次执行申请几次内存
性能优化建议
1.slice与map 预分配内存
NoPreAlloc
func NoPreAlloc(size int) {
data := make([]int, 0)
//data := make(map[string]int)
for i := 0; i < size; i++ {
data = append(data, i)
}
}
PreAlloc
func PreAlloc(size int) {
data := make([]int, size)
//data := make(map[string]int)
for i := 0; i < size; i++ {
data = append(data, i)
}
}
性能差异
原理
2.slice 大内存未释放
切片是引用,如果我们在一个大的切片上构造一个小的切片,那么大的切片仍在内存之中不会释放。
解决方法:使用copy函数
func ByCopy(origin []int) []int {
result := make([]int, 2)
copy(result, origin[len(origin)-2:])
return result
}
3.字符串处理
字符串拼接推荐使用
strings.Builder
,bytes.Buffer
性能更差
func StrBuilder(n int, str string) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
原理
字符串在Go语言中是不可变类型,占用内存大小是固定的。使用+每次都会重新分配内存。strings.Builder, bytes. Buffer底层都是[]byte 数组,有相应的内存扩容策略,不需要每次拼接重新分配内存。
4.空结构体
实现Set,可以考虑用map代替。
因为空结构体struct{}实例不占据任何的内存空间,节省空间。
set := make(map[string]struct{})
5.atomic包---关于锁的优化
- 锁的实现是通过操作系统来实现,属于系统调用
- atomic操作是通过硬件实现,效率比锁高
- sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量
- 对于非数值操作,可以使用atomic.Value,能承载一个interface{}
2.性能分析工具 pprof
pprof是用于可视化和分析性能、数据的工具
案例测试
运行测试项目:
https://github.com/wolfogre/go-pprof-practice
因为项目引入了net/http/pprof注入的入口,所以打开http://localhost:6060/debug/pprof,该页面上展示了可用的程序运行采样数据
- CPU
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"
top
查看占用资源最多的函数
list
根据指定的正则表达式查找代码行
- Heap
web界面更方便:
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"
source视图类似于之前的list
命令
采样数据:
- goroutine
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"
- mutex
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
- block
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"