第5、6次汇总

31 阅读6分钟

问:

如何编写更简洁清晰的代码

常用Go语言程序优化手段

熟悉Go程序性能分析工具

了解工程中性能优化的原则和流程

高质量的编程

高质量编程简介

编写的代码能够达到正确可靠、简介清晰的目标,就可以称为高质量代码

  • 边界条件考虑得完备吗
  • 异常情况处理,稳定性保证
  • 易读易维护
    • 1逻辑别太复杂,别人不太理解就没法修复改进
    • 2提高生产力

编码规范

  • 代码格式

    • 如gofmt可以自动格式化go语言代码为官方统一风格
    • goimports可以自动增删依赖的包引用、将依赖包按字母序列排序并分类
    • 自己在使用Goland的时候有过这样的体验,就是
  • 注释

    • 作用

      • 1注释了别人容易看懂
      • 2注释了以后自己看的时候,也更容易回忆起来,自己就亲身体验过,以前写的代码后来看不懂
    • 注释谁

      • 注释公共符号
      • 注释实现过程
      • 解释代码什么情况会出错
      • 小结
        • 代码是最好的注释
        • 注释应该提供代码未表达出的上下文信息
  • 命名规范

    • 变量名

      • 简洁胜冗长
      • 变量距离其被使用的地方月圆,则需要携带越多的上下文信息,如全局变量的名字
    • 函数名

      • 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的
        • 比如
          • http.Serve
          • http.ServeHTTP
          • 第二个就太重复了吧
    • 包名

      • 只由小写字母组成,不包含大写字母和下划线等字符
      • 简短并包含一定的上下文信息
      • 不要与标准库同名
      • 不要使用常用变量名作为包名(想法,万一后面有个人随手想定义个常用变量,结果这个已经作为包名了,后面可能会造成一些误解。
      • 使用单数而不是负数
      • 谨慎地使用缩写
  • 控制流程

    • 尽量保持正常代码路径为最小缩进(默认规定这种方式是正常流程,这样别人就不用还花力气判断哪个是正常代码路径了)

    • 故障问题大多出现先在复杂的条件语句和循环语句中

  • 错误处理

    • 简单的错误指的是仅出现一次的错误,且在其他地方不需要捕获该错误

      • 优先使用erros.New来创建匿名变量来直接表示简单错误
      • 如果有格式化的需求,使用fmt.Errof
    • 错误的Wrap和Unwrap

      • 这个是针对复杂一点的错误的,可以从这个链中找到问题的关键
      • 错误的Wrap实际上是提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链
      • 在fmt.Errof中使用%w关键字来讲一个错误关联至错误链中
    • 错误判定

      • 判定一个错误是否为特定错误的方法:error.Is
    • panic

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

      • 只能在被defer的函数中使用

      • 嵌套无法生效

      • 只在当前goroutine生效

      • defer的语句时后进先出

        • 这个可以理解成栈
      • 关于什么时recover

        • 在 Go 语言中,recover() 函数被用来处理程序的 panic 异常。当程序发生 panic 时,程序会立刻停止执行当前函数的任何代码,然后开始执行 deferred 函数,接着继续向上层函数传递这个 panic,直到某个函数处理了这个 panic 或者整个程序都被迫停止运行。

          使用 recover() 函数可以在当前函数内捕获 panic,并恢复程序的正常执行。recover() 必须在 defer 函数内被调用,否则它不会起作用。如果在 defer 函数中调用 recover(),并且当前函数发生了 panic,那么 recover() 将返回 panic 的值,并且阻止程序继续向上层函数传播 panic。如果当前函数没有发生 panic,或者当前函数没有包含 defer 函数,那么 recover() 将返回 nil。

          注意,recover() 只能在 defer 的函数体内直接调用,否则会失效。此外,当一个函数中的 panic 被 recover 处理后,整个程序并不会自动恢复正常执行状态。必须手动编写代码来实现恢复程序的正常执行状态。

性能优化建议

  • 先把前面的正确可靠的代码搞好
  • Benchmark
    • 如何使用
      • 性能表现需要实际数据衡量
      • Go语言提供了支持基准性能测试的benchmark工具
    • 性能优化之Slice
      • 切片本质是一个数组片段的描述
        • 包括数组指针
        • 片段的长度
        • 片段的容量
      • 切片操作并不复制切片指向的元素
      • 创建一个新的切片会复用原来切片的底层数组
        • 所以如果使用切片的使用冒号来截短原来切片,是会存在复用的,代码会在媛切片基础上新建小切片。而原底层数组在内存中有引用,内存就会得不到释放
    • 性能分析之map
      • map和slice是同理的
        • 不断向map中添加元素的操作会触发map的扩容
        • 提前分配好空间可以减少内存拷贝和Rehash的消耗
        • 所以建议根据实际需求提前预估好需要的空间
    • 字符串处理
      • 比较直接使用加号和strings.Builder和ByteBuffer,strings.Builder是时间最少的
      • 字符串在Go语言中是不可变类型,占用内存大小是固定的,每次使用加号,都会重新分配内存
      • strings.Builder和bytes.Buffer底层都是[]byte数组,内存扩容策略,不需要每次拼接重新分配内存
    • 空间优化之空结构体
      • 空结构提struct{}实例不占据任何的内存空间
      • 可作为各种场景下的占位符使用
        • 节省资源
        • 空结构提本身具备很强的语义,即这里不需要任何值,仅作为占位符
    • atomic包,在时间上优于加锁
      • 锁的实现是通过操作系统来实现,属于系统调用
      • atomic操作是通过硬件实现,效率比锁高
      • sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量
      • 对于非数值操作,可以使用atomic.Value,能承载一个interface{}
    • 听人劝
      • 避免常见的性能陷阱可以保证大部分程序的性能
      • 普通应用代码,不要一味地追求程序的性能
      • 越高级的性能优化手段越容易出现问题
      • 在满足正确可靠、简介清晰的质量的前提下提高程序性能