笔记-高质量编程与性能调优 | 青训营
Part1:高质量编程
注释
- 公共符号要注释,包括变量、常量、函数、结构体需要加注释
- 任何不明显也不简短的公共功能需要加注释
- 无论长度或复杂度如何,必须对库中的任何函数进行注释
- 解释代码的作用
- 解释代码的实现(相对复杂、不明显)
- 解释为什么要写这段代码(提供额外的上下文,发方便理解)
- 说明代码在什么情况下会出错和代码的限制条件
//Open opens the named file for reading.If successful, methods on the
//the return file can be used for reading; the associated file
//descriptor has make O_RDONLY
//If there is an error,it will be of type *PathError
代码格式
推荐工具:gofmt、goimports
- gofmt,Golang官方提供的工具,能够自动格式化GOlang代码为官方统一风格
- goimports,Golang官方提供的工具,在gomft的基础上实现自动增删依赖的包引用,将依赖包按字母顺序进行排列分类
命名规范
变量
- 简洁
- 缩略词全大写
- 变量距离被使用越远,名字本身需要携带更多上下文信息
函数
- 简短
- 函数名不携带包名信息
- 如果函数名的返回类型与包名不一致时可以在函数名中加入类型信息
http.Server
http.ServerHTTP
包
- 只有小写字母组成,不包含下划线
- 不与标准库同名
- 简单并包含一定上下文信息
- 不使用常用的变量名来命名
控制流程
- 避免嵌套
- 尽量优先处理错误情况或特殊情况,尽早返回或继续循环来减少嵌套
fun OneFunc() error{
if err := doSomething(); err != nil{
return err
} // 流程1
if err := doAnotherthing(); err != nil{
return err
} // 流程2
......
return nil
} // 避免嵌套
错误和异常处理
简单错误(只出现一次)
- 优先使用errors.New来创建匿名变量来直接表示简单错误
if len(via) >= 10 {
return errors.New("len of via more than 10)
}
错误的Wrap和Unwarp
- 错误的Wrap实际上提供了一个error嵌套另一个error的能力,从而形成了一个error的跟踪链,通过提供额外的上下文信息来确定错误的位置
- 在fmt.Errorf中使用%w关键词来将一个错误关联到错误链中
- 使用errors.ls来判断错误链上的所有错误是否包含特定错误
- 从错误链中获取特点种类错误,使用errors.AS
if err != nil{
return fmt.Errorf("reading srcfiles list: %w",err)
}
if errors.ls(err, fs.ErrNotExist){
......
return []byte,nil
}
return data, err
if errors.As(err,&PathError){
fmt.Println("Fail at path:PathError.Path")
}else{
fmt.Println(err)
}
Panic
在程序启动阶段发生了不可逆转的错误时,可以在init或main函数中使用panic,不要在业务逻辑中使用
recover
- 只能在被defer使用的函数中使用
- 嵌套无法生效
- 只能在当前goroutine中生效
- defer语句是后进先出的
Part2:性能优化建议
性能测试工具Benchmark
- 函数名
- 函数执行次数
- 每次执行函数所需时间
- 每次执行做需要申请的内存
- 每次执行要申请多次内存
go test -bench=. -benchmen
go test -run=. -v
func Fib(n int) int{
if n<2 {
return n
}
return Fib(n-1) + Fib(n-2)
}
func BenchmarkFib10(b *testing.B){
for n:= 10;n<b.N;n++{
Fib(10)
}
}
slice预分配内存
- 尽可能在make()初始化切片时提供容量信息
- 可以减少内存分配次数提升性能,因为如果没有指定内存大小,slice一开始自动分配的可能大小不够,这需要新建一个slice并复制过去,这会导致性能损失
- 在原有切片的基础上创建小切片用re-slice,防止大内存未释放问题
map预分配内存
- 尽可能在make()初始化map时提供容量信息
字符串处理
- 使用strings.Builder进行拼接字符串
- 因为字符串是不可变类型,使用+进行拼接底层上是创建一个新的字符串,来进行存储,这样会损失性能
- 如果能预知内存大小,可以使用Grow方法
func StrBuilder(n int, str string) string {
var builder strings.BUilder
for i:=0;i<n;i++{
builder.WriteString(str)
}
returm builder.String()
}
# Grow
func PreByteBuffer(n int,str string) string{
buf := new(bytes.Buffer)
buf.Grow(n*len(str))
for i:=0;i<n;i++{
buf.WriteString(str)
return buf.String()
}
}
空结构体
- 空结构体不占用任何内存空间
- 可以用作占位符使用
使用atomic包
- 多线程确保线程安全可以使用atomic包而不是加锁
- 因为锁是系统调用代价高,atomic是硬件实现,效率比较高
Part3:性能调优工具
性能分析工具pprof
- pprof是用于可视化和性能分析的工具,可以知道什么地方耗费了多少CPU、内存
- 第一步引用
"net/http"
-"net/http/pprof"
- 第二步调用
go func(){
log.Println(http.ListenAndSereve(":6060",nil))
}()
- 第三步访问
127.0.0.1:6060/debug/pprof/
- 也可以使用命令
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10" # CPU
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap" # 堆内存
......
- 进入到pprof中以后
top # 前5个最消耗资源的函数
list 函数名 # 找到这个函数
web # 使用网页模式
CPU
- flat:当前函数本身执行的时间
- flat%:当前函数执行占CPU总时间的百分比
- sum%:上面所有flat%之和
- cum:函数调用所需时间
- cum%:cum占CPU总时间的比例
- 如果flat==cum 函数没有调用其他函数
- 如果flat==0 函数只调用了其他函数