这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
今天主要是学习了高质量编程方法以及性能调优工具。性能调优工具还没有实践和总结,明天补齐。
高质量编程
什么是高质量?编写的代码能够达到正确可靠、简介清晰的目标可称之为高质量代码。
- 各种「边界条件」是否考虑完备
- 异常情况处理,稳定性保证
- 易读易维护
编程原则
- 简单性: 消除多余复杂性,过于复杂的代码难以维护
- 可读性: 编写可维护代码的第一步是确保代码可读
- 生产力: 团队整体工作效率非常重要
编程规范
代码格式
gofmt 是 Go 官方提供的格式化工具,常见 IDE 都可以方便配置。goimports 可以自动增删依赖的包引用、将依赖包按字母序排序并分类。
注释
注释应该做什么?
- 解释代码作用
- 解释代码如何做的
- 解释代码实现的原因(为什么这么做)
- 解释代码什么情况会出错(多想一下边界条件)
Good code has lots of comments, bad code requires lots of comments
命名规范
变量命名:
- 简洁胜于冗长
- 缩略词全大写,但其位于变量开头且不需要导出时,使用全小写。
- 例如使用 ServeHTTP 而不是 ServeHttp
- 例如使用 XMLHTTPRequest 或者 xmlHTTPRequest
- 变量距离被使用到的地方越远,则需要携带越多的上下文信息。全局变量在名字中需要更多的上下文信息
函数命名:
- 函数名不携带包名。
- 尽量简短。
- 当名为 foo 的包某个函数返回类型 Foo 时,可以省略类型信息而不导致歧义。
- 当名为 foo 的包某个函数返回类型 T 时(T 并不是 Foo),可以在函数名中加入类型信息。
package 命名:
- 只有「小写字母」,不包含大写字母和下划线等字符
- 简短并包含一定的上下文信息。例如 schema,task 等。
- 不要与标准库同名。例如不要使用 sync 或者 strings
以下规则尽量满足:
- 不适用常用变量名作为包名。例如使用 bufio 而不是 buf。
- 使用单数而不是复数。例如 encoding 而不是 encodings。
- 谨慎地使用缩写。例如使用 fmt 在不破坏上下文的情况下比 format 更加简短。
Good naming is like a good joke. If you have to explain it, it's not funny
控制流程
- 避免嵌套,保持正常流程清晰。
- 尽量保持正常代码流程为最小缩进
错误和异常处理
-
简单错误
- 简单错误是仅出现一次的错误,且在其他地方不需要捕获该错误。
- 优先使用
errors.New来创建匿名变量来直接表示简单错误。 - 如果有格式化的需求,使用
fmt.Errorf。
-
错误的 wrap 和 unwrap
- 错误的 wrap 实际上是提供了一个 error 嵌套另一个 error 的能力,从而生成一个 error 的跟踪链。
- 在 fmt.Errorf 中使用: %w 关键字来将一个错误关联至错误链中。
-
错误判定
- 使用 errors.Is 来判定一个错误链上是否含有特定的错误。
- 使用 errors.As 来获取某种特定种类的错误。
-
panic
- panic 一般不在业务代码中使用。因为 panic 发生后会向上传播到调用堆栈,如果当前 goroutine 中所有 defered 函数都不包含 recover 就会造成整个程序奔溃。如果问题可以被解决,建议使用 error 代替 panic。
- 当程序启动阶段发生不可逆转的错误时,可以在 init 或 main 函数中使用 panic。
-
recover
- recover 只能在被 defer 的函数中使用
- 嵌套无法生效
- 只在当前 goroutine 生效
- defer 的语句是后进先出
- 如果需要更多的上下文信息,可以 recover 后在 log 中记录当前的调用栈
性能优化建议
Benchmark
go 提供了支持基准性能测试的 benchmark 工具
go test -bench=. -benchmem
slice
与 C++ STL vector 等一样,使用前尽可能预分配内存。
另外,在已有切片上构建新分片时,如果原切片较大,代码在原切片基础上新建小切片而很久不释放,那么原切片会由于有引用得不到释放。这时,可以使用 copy 代替 re-slice。
map
与 slice 一样,使用前可以预分配内存,减小 rehash 的消耗。
字符串处理
使用 strings.Builder 来拼接字符串,比使用 + 更快。因为在 Go 中,string 是不可变类型,占用内存大小是固定的。
空结构体
空结构体 struct{} 实例不占任何的内存空间,可以在很多场景下充当占位符节省资源。
一个例子是,可以用 Map 来实现 Set。
atomic 包
相比于使用锁,atomic 通过硬件来直接控制变量,效率更高。
sync.Mutex 用来保护一段逻辑,不仅仅用于保护一个变量。
对于非数值操作,可以使用 atomic.Value,能承载一个 interface{}