这是我参加青训营的第 3 天
高质量编程
代码规范
变量命名规范
· 简洁胜于冗长
· 缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写
· 例如使用 ServerHTTP 而不是 ServerHttp
· 使用XMLHTTPRequest 或者 xmlHTTPRequest
· 变量距离其被使用的地方越远,则需要携带越多的上下文信息
· 全局变量在其名字中需要更多的上下文信息,使得在不同的地方可以轻易辨认出其含义
函数命名规范
· 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现
· 函数名尽量简短
· 当名为 foo 的包某个函数返回类型 Foo 时,可以省略类型信息而不导致歧义
· 当名为 foo 的包某个函数返回类型 T 时(T 并不是 Foo),可以在函数名中加入类型信息
package命名规范
· 只由小写字母组成。不包含大写字母和下划线等字符
· 简短并包含一定的上下文信息。例如 schema、task等
· 不要与标准库同名。例如不要使用 sync 或者 strings
尽量满足:
· 不使用常用变量名作为包名。例如使用 bufio 而不是 buf
· 使用单数而不是复数。例如使用 encoding 而不是 encodings
· 谨慎地使用缩写。例如使用 fmt 在不破坏上下文的情况下,比 format 更简短
错误和异常处理
Go1.13 在 errors 中新增了三个新API和一个新的 format 关键字,分别是 errors.ls errors.As,errors.Unwrap 以及 fmt.Errorf 的 %w。
如果项目运行在小于Go1.13的版本中,导入 golang.org/x/xerrors 来使用
fmt.Errorf
在 fmt.Errorf 中使用: %w 关键字将一个错误关联至错误链中,示例:
return fmt.Errorf("reading srcfiles list: %w", err)
errors.Is
判断一个错误是否为特定错误
不同于使用 ==, 使用该方法可以判定错误链上的所有错误是否含有特定错误
示例:
if errors.Is(err, fs.ErrNotExist) {
return []byte{}, nil
}
errors.As
在错误链上获取特定种类的错误,示例:
if errors.As(err, &pathError) // 获取pathError的特定错误
panic
用于真正的异常情况
· 不建议在业务代码中使用 panic
· 调用函数不包含 recover 会造成程序崩溃
· 若问题可以被屏蔽或解决,建议使用 error 代替 panic
· 当程序启动阶段发生不可逆转的错误时,可以在 init 或 main 函数中使用 panic
// Paincf is equivalent to Printf() followed by a call to panic().
func Panicf(format string, v ...interface{}) {
s := fmt.Sprintf(format, v...)
std.Output(2, s)
panic(s)
}
recover
· recover 只能在被 defer 的函数中使用
· 嵌套无法生效
· 只在当前 goroutine 生效
· defer 的语句是后进先出
重要一点:如果需要更多的上下文信息,可以 recover 后在 log 中记录当前的调用栈
func (t *treeFS) Open(name string) (f fs.File, err error) {
defer func() {
if e := recover(); e != nil {
f = nil
err = fmt.Errorf("gitfs panic: %v\n%s", e, debug.Stack())
}
}()
//……
}
defer
1. defer 语句会在函数返回前调用
2. 多个 defer 语句都是后进先出
func main() {
if true {
defer fmt.Println("1")
} else {
defer fmt.Println("2")
}
defer fmt.Println("3")
} // 输出结果为
// 3
// 1
性能优化
Benchmark
运行语句:
go test -bench=. -benchmem
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n-1) + Fib(n-2)
}
func BenchmarkFib10(b *testing.B) {
for n := 0; n < b.N; n++ {
Fib(10)
}
}
slice
预分配内存,在使用make() 初始化切片时提供容量信息。这样可以使代码效率提高百分之三十往上
func NoPreAlloc(size int) {
data := make([]int, 0, size)
for k := 0; k < size; k++ {
data = append(data, k)
}
}
大内存未释放
· 在已有的切片数组上创建切片不会创建新的底层数组
· 可使用 copy 代替 re-slice
func GetLastBySlice (origin []int) []int {
return origin[len(origin) - 2:]
}
// 改进
func GetLastByCopy (origin []int) []int {
result := make([]int, 2)
copy(result, origin[len(origin) - 2:])
return result
}
// 内存使用情况可以减轻将近97%
strings.Builder
处理字符串拼接
func StrBuilder(n int, str string) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
// 当预先知道字符串长度时,使用预处理来优化
func PreStrBuilder(n int, str string) string {
var builder strings.Builder
// 计算好字符串大小
builder.Grow(n * len(str))
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
空结构体
· 空结构体 struct{} 实例不占据任何的内存空间
· 可作为各种场景下的占位符使用
· 节省资源
· 空结构体本身具备很强的语义,这里不需要任何值,仅作为占位符
func BoolMap(n int) {
m := make(map[int]bool)
for i := 0; i < n; i++ {
m[i] = false
}
}
// 使用空结构体改进
func EmptyStructMap(n int) {
m := make(map[int]struct{})
for i := 0; i < n; i++ {
m[i] = struct{}{}
}
}
atomic 包
当单变量进行变化的时候,一般会选择来加锁对变量数值进行保护
但是加锁主要是保护一段逻辑,单纯保护一个变量的变化情况会过于浪费
使用 atomic 包可以解决这个问题,并且效率还高于加锁
对于非数值操作,可以使用 atomic.Value,能承载一个 interface{}
// 高效率,不浪费
type atomicCounter struct {
i int32
}
func AtomicAddOne(c *atomicCounter) {
atomic.AddInt32(&c.i, 1)
}
// 加锁
type mutexCounter struct {
i int32
m sync.Mutex
}
func MutexAddOne(c *mutexCounter) {
c.m.Lock()
c.i++
c.m.Unlock()
}
性能优化工具
pprof
运行命令:
go tool pprof URL
示例:
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10" // 获取指标
命令: topN
查看占用资源最多的函数
· Flat == Cum,函数中没有调用其他函数
· Flat == 0, 函数中只有其他函数的调用
命令:list
根据指定的正则表达式查找代码行,例:
list Eat
业务优化
流程
- 建立服务性能评估手段
- 分析性能数据,定位性能瓶颈
- 重点优化项改造
- 优化效果验证
性能评估手段
· 服务性能评估方法
· 单独 benchmark 无法满足复杂逻辑分析
· 不同负载情况下性能表现差异
· 请求流量构造
· 不同请求参数覆盖逻辑不同
· 线上真实流量情况
· 压测范围
· 单击器压测
· 集群压测
· 性能数据采集
· 单机性能数据
· 集群性能数据
分析性能数据,定位性能瓶颈
· 使用库不规范
· 高并发场景优化不足
重点优化项改造
· 正确性是基础,在优化代码的情况下,一定要保证正确性为前提
· 响应数据 diff
· 线上请求数据录制回放
· 新旧逻辑接口数据 diff
优化效果验证
· 重复压测验证
· 上线评估优化效果
关注服务监控
逐步放量
收集性能数据
服务整体链路分析
· 规范上游服务调用接口,明确场景需求
· 分析链路,通过业务流程优化提升服务性能
AB实验SDK的优化
分析基础库核心逻辑和性能瓶颈
设计完善改造方案
数据按需获取
数据序列化协议优化
内部压测验证
推广业务服务落地验证