这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记。
本篇笔记为第三节课 Go 语言高性能与性能调优课程的笔记。
高质量编程
高质量:正确可靠、简洁清晰
- 边界条件是否完备
- 异常情况处理,稳定性保证
- 易读易维护
编程原则
- 简单性
- 可读性:Go 语言强制统一代码格式
- 生产力
代码格式、注释、命名规范、控制流程、错误和异常处理
代码格式:
- gofmt 官方工具自动格式化代码
- goimports:gofmt+自动依赖话管理
注释:
-
解释代码作用:公共符号(函数)
- 包中声明的每个公共符号(变量、常量、函数、结构)始终要注释
-
解释如何做的:代码实现过程
-
代码实现的原因:解释代码的外部因素,提供额外上下文信息
-
什么情况下会出错:解释代码的限制条件
命名规范:
降低阅读和理解代码的成本;重点考虑上下文信息,设计简洁清晰的名称
“好的命名像一个笑话,需要解释的不是好笑话。”
变量命名规范:
- 简洁
- 缩略词全大写(例:ServerHTTP);在变量开头且不需要导出时使用全小写(例:xmlHTTPRequest)
- 变量作用域越远需要携带越多的上下文信息(如:用 deadline 代替 t)
函数命名规范
- 函数名不携带包名,因为包名和函数名成对出现(
http.Server) - 简洁
- 名为 foo 的包中的函数返回类型 Foo 时,可以省略类型信息;名为 foo 的包中的函数返回类型 T 时,可以省略类型信息
package命名规范
- 小写字母组成,不包含大写字母和上下文
- 简短并包含一定上下文信息(schema、task)
- 不要玉标准库同名(sync、string)
- 尽量不使用常用变量名(如用 bufio 代替 buf)
- 尽量使用单数(如用 encoding 而不是 encodings)
- 尽量谨慎使用缩写(如用 fmt 代替 format)
控制流程:
- 避免嵌套
- 优先处理错误流程,提前返回;保证正常流程的路径为最小缩进。
错误和异常处理
简单错误:仅出现一次的错误,其它地方不用捕获
- errors.New()创建匿名变量直接表示
- fmt.Errorf()格式化
return errors.New("Error!")
错误的 Wrap 和 Unwrap:一个 error 嵌套另一个,生成 error 的跟踪链
- fmt.Errorf()中使用%w 来将一个错误关联至错误链中
if err != nil{
return fmt.Errorf("Error %w!", err)
}
错误判定:
- error.Is:判断错误链中所有错误是否包含特定错误(不同于==)
if error.Is(err, fs.ErrNotExist)
- error.As:错误链中获取特定种类的错误
var pathError *fs.PathError
error.As(err, &pathError)
fmt.PringLn("Failed at path: ", pathError)
-
panic:程序无法正常工作
- 不建议业务代码中使用,不包含 recover 会造成程序崩溃,能处理的使用 error
- 启动阶段发生不可逆转的错误时,在 init 或 main 中使用 panic
-
recover:处理 panic 影响逻辑
- 只能在当前 goroutine 的被 defer 的函数中使用,嵌套不生效
- recover 后可以在 log 记录当前的调用栈
if e := recover(); e != nil{
err = fmt.Errorf("panic: %v\n%s", e, debug.Stack())
}
多个 defer 的输出后进先出
Go 性能优化
- 基准性能测试:benchmark
- slice 切片预分配内存
- slice 本质:数组片段的描述,包含数组指针、片段长度、片段容量
- 切片操作不复制切片指向的元素
data := make([]int, 0, size)
-
原数组较大时使用 copy 代替 re-slice
- 创建新切片复用原来切片的底层数组
- 原数组在内存中有引用,得不到释放
- map 预分配内存
- 向 map 不断添加元素会触发 map 的扩容,发生内存拷贝和 rehash
data := make(map[int]int, size)
- 字符串
- 使用 strings.Builder 或 bytes.Buffer 代替直接字符串拼接(+)
- 字符串在 Go 中不可变,+每次重新分配内存
- strings.Builder 或 bytes.Buffer 底层都是 []byte 数组,扩容策略不需要每次重新分配内存
var builder strings.Builder
builder.WriteString(str)
return builder.String()
buf := new(bytes.Buffer)
buf.WriteString(str)
return buf.String()
- 空结构体(
struct{}{})可以作为占位符,不占内存
- 空结构体不占内存,bool 类型也会占1字节
- 如:使用 map 来实现 Set,只用到键,用不到值
- atomic 包
- atomic 是硬件实现,锁是系统调用,通过操作系统实现,因此 atomic 的效率比锁高
- sync.Mutex 用于保护一段逻辑而不仅是变量
- 非数值操作可以使用 atomic.Value,可以承载一个 interface{}