Lesson9 高质量编程与性能优化建议|青训营笔记

26 阅读3分钟

7.1 高质量编程

什么是高质量——编写的代码正确可靠、简洁清晰(边界条件、异常处理、可维护性)

  • 注释

    包中声明的每个公共的符号,不需要注释实现接口的方法

    解释代码的作用、如何做的、实现的原因、什么情况会出错

  • 代码格式

    建议使用gofmt自动格式化代码

  • 命名规范

    简洁胜于冗长

    驼峰命名法

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

    包名由小写字母构成,不要与标准库同名,使用单数而不是复数

  • 控制流程

    避免嵌套,保持正常流程清晰

  • 异常处理

    Wrap和Unwrap

    不建议在业务代码中使用panic,建议使用error代替panic

    recover只能在被defer的函数中使用(defer后进先出)

7.2 性能优化建议

性能优化的前提是满足正确可靠、清晰简洁等质量因素

性能优化是综合评估,有时候时间效率和空间效率可能对立

针对Go语言特性,介绍Go相关的性能优化建议

Benchmark

Go语言提供了支持基准性能测试的benchmark工具

go test -bench=. -benchmem

优化建议 - Slice:

切片的本质是一个数组片段的描述,包含以下三项:

  • 数组指针 array unsafe.Pointer
  • 片段的长度 len
  • 片段的容量 cap (不改变内存分配情况下的最大长度)

尽可能在使用 make() 初始化切片时提供容量信息。这是因为向切片中添加的元素数量超过默认容量会触发扩容机制,扩容是一个比较耗时的操作。

切片使用陷阱:大内存未释放

  • 场景:

    • 原切片较大,代码在原切片基础上新建小切片。
    • 原底层数组在内存中有引用,得不到释放。

这是由于 Golang 中在已有切片的基础上创建切片,不会创建新的底层数组,而是直接复用原来的。如果只是需要用到其中的一小部分,复用原来的整个数组会导致占用较大的内存空间,建议使用 copy 替代 re-slice。

优化建议 - Map:

同样的,map 也建议预分配内存来避免扩容机制的时间开销。

  • 不断向 map 中添加元素会触发 map 的扩容。
  • 提前分配好空间可以减少内存拷贝和 Rehash 的消耗。
  • 建议根据实际需求提前预估好需要的空间。
优化建议 - 字符串处理:

和 Java 语言类似,Golang 中直接使用 + 拼接字符串是一种十分低效的方式,因为字符串是不可变类型,使用 + 每次都会重新分配内存,推荐使用 strings.Builderbytes.Buffer 操作字符串(strings.Builder 效率要更高一些)。

优化建议 - 空结构体:

使用空结构体 struct{} 可以节省内存。

  • 空结构体实例不占据任何的内存空间。

  • 可作为各种场景下的占位符使用。

    • 节省资源。
    • 空结构体本身具备很强的语义,即这里不需要任何值,仅作为占位符。

比如在实际的开发中,我们经常会使用到 Set 这种数据结构,然而 Golang 本身并不支持 Set,我们可以考虑用 map 来代替。换句话说我们只用到 map 的键,而不用它的值,那么值可以用 struct{} 类型占位。

优化建议 - atomic 包:

atomic 包主要用在多线程编程,相比于加锁的方式来保证并发安全,atomic 包效率更高。

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