Go高质量编程|青训营笔记

119 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记。

  1. 高质量编程简介

    • 各种边界条件是否考虑完备
    • 异常情况处理是否得当
    • 是否保证稳定性
    • 是否易读易维护
  2. 编码规范

    • 代码格式:

      gofmt:Go语言官方工具,自动格式化代码为官方统一风格

      goimports:官方工具,在gofmt基础上增加了自动增删依赖包的引用,并将依赖包按字母排序分类

    • 注释:

      • 解释代码作用
      • 解释代码实现过程
      • 解释代码实现原因,需要适当补充代码的外部因素,提供需要的额外上下文
      • 解释代码的出错情况or限制条件
      • 公共符号or功能一定要注释(实现接口的方法除外)
    • 命名规范:

      1. 变量:

        • 简洁胜于冗长

          // Bad
          for index := 0; index < len(s); index++ {
          	// something
          }
          // Good
          for i := 0; i < len(s); i++ {
              // something
          }
          

          由于作用范围较小且意思直接明了,这里index相对于i几乎没有增加对于层序的理解

        • 缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写

        • 变量距离其被使用越远,则需要携带越多的上下文信息

          // Bad
          func (c *Client) send(req *Request, t time.time)
          // Good
          func (c *Client) send(req *Request, deadline time.time)
          

          t通常表示任意时间,而deadline表述截止时间,有特定含义,如果在较远的地方使用时更清晰明了

      2. 函数:

        • 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现
        • 函数名尽量简短
        • 函数名需要体现返回值类型信息(当返回值类型与包名一致时除外)
      3. 包:

        • 只由小写字母组成,不包含大写字母与下划线等字符
        • 简短并包含一定上下文信息
        • 不要与标准库同名
        • 最好不要使用常用变量名(a、b、buf等)
        • 使用单数而不是复数
        • 在不破坏上下文的情况下可以使用缩写
    • 控制流程:

      • 线性原理,处理逻辑尽量走直线,避免复杂的嵌套分支
      • 正常流程代码沿着屏幕向下移动
      • 提升代码可维护性和可读性
      • 故障问题大多出现在复杂的条件语句和循环语句中
    • 错误和异常处理

      1. 简单错误(出现较小的错误):

        • 使用errors.New创建匿名变量直接表示
        • 可使用fmt.Errorf格式化错误
      2. 错误的Wrap和Unwrap:

        • 错误的Wrap实际上是提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链
        • 在fmt.Errorf中使用:%w关键字来将一个错误关联至错误链中
      3. 错误判定:

        • 使用errors.Is判定一个错误是否为特定错误
        • 使用errors.As在错误链上获取特定种类的错误
      4. panic

        • 不建议在业务代码中使用panic
        • 调用函数不包含recover会造成程序崩溃
        • 在问题可以被屏蔽或解读时,建议使用error代替panic
        • 当程序启动阶段发生不可逆转的错误时,可以在init或main函数中使用panic
      5. recover

        • recover只能在被defer函数中使用
        • 嵌套无法生效
        • 只在当前goroutine生效
        • 如果需要更多上下文信息,可以recover后在log中记录当前的调用栈
  3. 性能优化建议

    1. 简介

      • 性能优化的前提是满足正确可靠、简洁清晰等质量因素
      • 性能优化是综合指标,有时候时间效率和空间效率可能对立
      • 针对Go语言特性,介绍Go相关的性能优化建议
    2. 工具

      • Benchmark工具
    3. slice优化建议

      • 尽可能在使用make()初始化切片时提供容量信息(减少申请内存次数)
      • 在原有切片基础上创建切片用copy代替re-slice(避免大内存未释放)
      • 原理见geektutu.com/post/hpg-sl…
    4. Map优化建议

      • 预分配内存(减少申请内存次数)
    5. 字符串处理

      • 分析:

        • 字符串在Go中是不可变类型,占用的内存大小是固定的
        • 使用+进行字符串拼接每次都会重新分配内存
        • strings.Builder,bytes.Buffer底层都是[]byte数组
      • 使用+拼接字符串性能最差,strings.Builder和bytes.Buffer相近,strings.Buffer更快

    6. 空结构体

      • 空结构体struct{}实例不占据任何的内存空间,可做占位符使用

        // for example
        // 通过map实现set
        m := make(map[int]struct{})
        
        // 使用bool会每个元素多占一个字节,其他数据类型更多
        m := make(map[int]bool)
        

        在这个场景中只用到了map的key值,并不需要value值,就可以使用空结构体作占位符

    7. atomic包

      • 锁的实现是通过操作系统实现来实现的,属于系统调用
      • atomic操作是通过硬件实现,效率比锁高
      • sync.Mutex更应该用来保护一段逻辑,而不是一个变量
      • 对于非数值操作,可以使用atomic.Value,能承载一个interface{}