高质量编程与性能调优笔记 | 青训营

56 阅读4分钟

高质量编程与性能调优

高质量定义: 编写的代码能够达到正确可靠、简洁清晰的目标可称之为高质量代码

三个考量

  • 是否考虑齐全边界条件
  • 对异常情况如何处理,能否保证稳定性
  • 是否易读易维护(对别人而言,也是对自己而言)

Go语言开发者Dave Cheney认为的编程三大原则:

  • 简单性 (简单直接清晰的逻辑来编写代码)
  • 可读性 (自己和别人是否都可读)
  • 生产力 (团队整体效率胜过单打独斗) (三大原则适用于任何编程语言)

五大方向确定Go代码高质量程度

  • 代码格式
  • 注释
  • 命名规范
  • 控制流程
  • 错误和异常处理

代码格式

public的变量,常量,函数以及结构都要添加注释 任何及不明显也不简短的公共功能必须加注释 库中任何函数无论长短复杂程度都必须加注释

Go语言官方提供工具: gofmt:自动格式化Go语言代码为官方统一风格 goimports: 等于gofmt加上依赖包管理,自动增删依赖的包应用、将依赖包按字母序排序并分类

注释

注释四个作用:

  • 解释代码作用
    • 注释公共符号
  • 解释代码如何做的
    • 实现过程
  • 解释代码实现的原因
    • 解释代码的外部因素
    • 提供额外上下文
  • 解释代码什么情况会出错
    • 解释代码的限制条件

命名规范

Variable

  • 简介胜过冗长
  • 缩写词全大写(ServeHTTP),如果不需要导出,使用全小写(XMLHTTPRequest 或xmlHTTPRequest)
  • Variable距离被使用的地方越远,命名中要携带越多上下文信息,使不同地方可以轻易便认出含义

Function

  • 函数名不加package name的上下文信息
  • 函数名尽量简短
  • 当名为foo的包某个函数返回Type为Foo时,可以省略类型信息而不导致歧义
  • 当名为foo的包某个函数返回Type为T(非Foo)时,可以在函数名中加入类型信息

package

  • 只由小写字母组成,不包含大写字母和下划线等符号
  • 简短并包含一定的上下文联系,例如schema、task
  • 不要与标准库同名
  • 以下规则尽量满足
    • 不使用常用变量名作为包名
    • 使用单数而不是复数
    • 谨慎使用缩写

控制流程

避免嵌套,保持正常流程清晰(比如if就return的话,就不用再else) 尽量保持正常代码路径为最小缩进(优先处理错误情况、特殊情况,尽早return或continue loop来减少嵌套) 故障问题大多出现在复杂的条件语句和循环语句中

错误和异常处理

  • 简单错误(仅出现一次error,且不需要catch error):
    • 优先使用errors.New来创建匿名变量来直接表示简单错误
    • 如果有格式化的需求,使用fmt.Errorf
  • 复杂错误/错误的Wrap和Unwrap(进行包装和解包):
    • 错误的Wrap提供了一个error嵌套另一个error的能力,从而生成一个error的追踪链
    • 在fmt.Errorf中使用%w关键字来讲一个错误关联至错误链中 example:
    list, _, err := c.GetBytes(cache.Subkey(a.actionID, "srcfiles"))
    if err != nil{
    	return fmt.Errorf("reading srcfiles list: %w", err)
    }
    
  • 错误判定
    • 判定一个错误是否为特定错误,使用errors.Is
    • 不同于使用==,该方法可以判定错误链上所有的错误是否含有特定的错误
    data, err = lockedfile.Read(targ)
    if errors.Is(err, fs.ErrNotExist){
    	//Treat non-existent as empty, to bootstrap the "latest" file
    	// the first time we connect to a given database.
    	return []byte{}, nil
    }
    return data, err
    
    • 在错误链上获取特定种类的错误,使用errors.As
    // 抓到pathError这一类
    if errors.As(err, &pathError){
    	// 解析打印出来出现error的具体path
    	fmt.Println("Failed at paht:", pathError.Path)
    }
    
  • Panic*
    • 用于真正异常的情况(程序崩溃时)
    • 不要使用panic, 如果真要用,必须用recover,不然会造成程序崩溃
    • 当程序启动阶段发生不可你转的错误时,可以在init或main函数中使用panic
  • Recover*
    • 只能在被defer的函数中使用
    • 嵌套无法生效
    • 只在当前goroutine生肖
    • defer的语句是后进先出
    • 目的是recover在log中记录panic后,当前的调用栈

性能优化建议

  • 性能量化工具:Benchmark,Go提供了支持基准性能测试的benchmark工具 go test -bench=. -benchmem
  • Slice,map等数据结构,对内存尽量提前预估,预分配内存,尽量避免扩容。
  • 字符串处理,用string builder或者byte builder,两者都是对byte进行操作。不要用string+=进行操作,因为string是immutable type,每次都会重新allocate memory加上复制旧data
  • 尽量使用空结构体struct{}节省内存,instance不占内存
  • 使用atomic进行多线程操作
  • 越高级的性能优化手段越容易出现问题

性能调优原则

  • 依靠数据不是猜测
  • 定位最大瓶颈而不是细枝末节
  • 不要过早优化
  • 不要过度优化

性能分析工具pprof 可视化和分析性能和数据的工具,可以知道程序在什么地方耗费多少CPU,内存