学会go的高质量编程

本文探讨了高质量编程的关键要素,包括编程原则(如简单性、可读性)、编码规范(如代码格式、注释、命名约定)、控制流程的最佳实践以及错误和异常处理。此外,还提供了性能优化建议,如基准测试、内存管理与使用pprof进行性能分析。最后,针对业务服务优化给出了具体实践案例。
摘要由CSDN通过智能技术生成

高质量编程

简介

  • 编写的代码能够达到正确可靠、简洁清晰的目标可以称之为高质量代码
  • 需要考虑各种边界条件是否完备
  • 异常情况处理是否妥善,能否保证稳定性
  • 是否易读易维护

编程原则:

  • 简单性
    • 消除“多余的复杂性”,以简单清晰的逻辑编写代码
    • 不理解的代码无法修复改进
  • 可读性
    • 编写可维护代码的第一步是确保代码可读
  • 生产力
    • 团队整体工作效率非常重要

编码规范

代码格式

  • 使用 gofmt 自动格式化代码
  • 使用 goimport 实现自动增删依赖的包引用、将依赖包按字母序排序并分类

注释

  • 注释应该解释代码的作用
  • 注释应该解释代码如何的(实现过程)
  • 注释应该解释代码实现的原因
    • 解释代码的外部因素
    • 提供额外上下文
  • 注释应该解释代码什么情况会出错
  • 公共符号始终要注释
    • 变量、常量、函数以及结构体
    • 任何既不明显也不简短的公共功能
    • 无论长度或复杂长度,库中的任何函数必须进行注释
    • 不需要注释实现接口的方法

命名规范

  • 变量命名规范
    • 略缩词全部大写,但当其位于变量开头且不需要导出时,使用全小写
      • 例如使用 ServeHTTP 和 XMLHTTPRequest 而不是 ServeHttp 和 xmlHTTPRequest
    • 变量距离其被使用的地方越远,则需要携带更多的上下文信息
      • 如全局变量在其名字中应该包含更多上下文信息
    • 对于一些只是局部变量的,不包含特殊含义的,没有必要使用全程,只需要特定的单词或者字母缩写替代即可,如 for 循环中的 i
    • 对于方法参数中带有特殊含义的,不要将其替换为简写,因为这会降低变量名的信息量
  • 函数名命名规范
    • 函数名不携带报名的上下文信息,因为包名和函数名是成对出现的
    • 当名为 foo 的包的某个函数返回类型为 Foo 时,可以省略类型信息避免导致歧义
    • 当名为 foo 的包的某个函数返回类型为 T 而不是 Foo 的时候,可以在函数名中加入类型信息
  • 包名命名规范
    • 只由小写字母组成,不能包含大写字母和下划线
    • 在包含一定的上下文信息的前提下,做到包名尽量简短
    • 不要和标准库同名
    • 不要使用常用变量名作为包名
    • 使用单数而不是复数
    • 谨慎使用缩写,需要保证不破坏上下文的情况下使用缩写

控制流程

  • 避免无意义的嵌套如
  •   if foo{
          return true
      }else {
          return false
      }if foo{
          return true
      }
      return false
    
  • 尽量保持正常代码路径为最小缩进
    • 优先处理错误情况/特殊情况,使得可以尽早返回或继续循环来减少嵌套
    • 使用线性原理——处理逻辑尽量走直线
    • 保证正常流程代码沿着屏幕向下移动

错误和异常处理

  • 简单错误:仅出现一次的错误,且在其他地方不需要捕获该错误
    • 优先使用 errors.New 来创建匿名变量来直接表示简单错误
    • 如果需要格式化使用 fmt.Errorf
  • 复杂错误
    • 使用 Wrap 和 Unwrap
    • Wrap 的原理实际上是提供了一个 error 嵌套另一个 error 的能力,从而生成跟踪链
    • 在 fmt.Errorf 可以使用 %w 关键字来讲错误关联至错误链中
  • 错误判定
    • 判断是否是特定错误可以使用 errors.is
    • 与 == 的区别时可以判断错误链上的所有错误
    • 使用 errors.As 方法可以从错误链上获得特定种类的错误
  • panic 时在程序启动阶段发生不可逆转的错误的时候,才考虑在 init 或 main函数中使用 panic,但是如果调用函数不包含 recover 会造成程序崩溃,因此如果可以被覆盖建议使用 error
  • recover
    • recover 只能在被 defer 的函数中使用
    • 嵌套无法生效
    • 只在当前的 goroutine 生效
    • defer 的语句是后进先出

性能优化建议

  • 可以使用 benchmark 进行分析,得到执行的次数、每次执行花费的时间、每次执行申请多大的内存、每次执行申请几次内存
  • slice 在使用的时候尽可能提供容量信息,预分配内存会提高速度
    • 切片本质上是一个数组片段的描述,他并不复制切片指向的元素,而是会创建一个新的切片复用原来切片的底层数组,这样就会导致增加时间,同时因为是在已有的切片基础上创建切片,那么原本的底层数组在内存中有引用得不到释放,就会占用内存,此时可以用 copy 替代 re-slice 方法
  • map 也推荐预分配内存
    • 由于向 map 中不断添加元素的操作会触发 map 的扩容,因此提前分配好内存空间可以减少内存拷贝和 rehash 的消耗
  • 在进行字符串拼接的时候,建议使用 strings.Builder 而不要直接使用 +
    • 原因是在于字符串在 Go 语言中是不可变类型,占用内存的大小是固定的,使用 + 进行操作会导致每次都需要重新分配内存,而 strings.Builder 和 byte.Buffer 的底层都是 byte 数组,因此不需要每次拼接都重新分配内存
  • 使用空结构体来节省内存
    • 因为空结构体的实例不占据任何的内存空间,可以作为任何场景下的占位符使用
    • 可以用在 map 实现 set
  • atomic 包
    • 由于锁的实现是基于系统的调用,而 atomic 操作是属于硬件的实现,因此atomic 的效率要远远高于锁的效率
    • 相较于使用sync.Mutex, 是应该用来保护一段逻辑的,而不仅仅用于保护一个变量,仅仅保护一个变量的话使用 atomic 即可
    • 对于非数值操作,可以使用 atomic.Value

性能调优实战

  • 原则:
    1. 要依靠数据不是猜测
    2. 要定位最大瓶颈而不是细枝末节
    3. 不要过早优化
    4. 不要过度优化

pprof

  • flat == cum 代表没有调用其他函数,flat == 0 代表函数只有其他函数的调用

性能调优案例

  • 业务服务优化
    • 服务:能单独部署,承载一定功能的程序
    • 依赖:对某个响应结果有需求
    • 调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系
    • 基础库:公共的工具包、中间件
  • 28
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值