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

90 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天

高质量编程

编码规范

代码格式工具

  • gofmt:GO官方的工具,自动格式化代码
  • goimports:等于gofmt + 依赖包管理,自动增删依赖包的引用,并按照字母顺序排序

注释

  1. 应解释:代码作用、代码是如何做的、实现原因、出错情况
  2. 注释应该提供代码未表达出来的上下文信息

命名规范

变量命名

  1. 缩略词全部大写,当其位于变量开头且不需要导出时,使用全小写
    1. ServerHTTP、ServerHttp
    2. XMLHTTPRequest或者xmlHTTPRequest
  2. 变量距离其被使用的地方越远,则需要携带越多的上下文信息 image.png

函数命名

  1. 尽可能简短
  2. 不需要携带包名的上下文信息
  3. 当名为foo的包某个函数返回类型为Foo时,可以省略类型信息而不导致歧义
  4. 当名为foo的包某个函数返回类型为T(T!=Foo)时,可以在函数名中加入该类型信息

包命名

  1. 不能与标准库重名
  2. 只能由小写字母构成
  3. 包含一定的上下文信息
  4. 使用单数而不是复数
  5. 不使用常用变量作为包名:bufio、buf
  6. 谨慎使用缩写:fmt

控制流程

  1. 避免嵌套
  2. 尽量保证正常路径位最小缩进
    1. 有限处理错误与特殊情况,尽早返回

错误处理

简单错误

不需要捕获的错误

  1. 优先使用errors.New()来创建匿名变量直接表示简单错误
  2. 格式化需求则使用fmt.Errorf image.png

错误链

  1. 将错误嵌套起来,Wrap和Unwrap实现error的跟踪链
  2. 在fmt.Errorf中使用%w将一个错误关联到错误链里 image.png

(GO1.13 errors中新增了errors.Wrap、errors.Is、errors.As、errors.Unwrap)

错误判断

  1. 使用errors.Is判断错误链上所有错误中是否含有特定的错误
  2. 不要使用==
image.png
  1. 使用errors.As获取错误链上某种特殊的错误,并取出他的内容

panic

  1. 业务代码中不建议使用
  2. 在程序启动阶段发生不可逆转错误时,可以在init()或者main()中使用

性能调优

Benchmarks

函数格式:func BenchmarkXxx(*testing.B)

func BenchmarkRandInt(b *testing.B) {
    for i := 0; i < b.N; i++ {
        rand.Int() 
    }
}

基准测试函数必须运行目标代码b.N次。在基准测试执行期间,b.N会被调整,直到基准测试函数持续足够长的时间,可以可靠地计时为止 go test -bench=BenchmarkRandInt(测试的函数名) image.png go test -bench=. -benchmem image.png

性能优化建议

内存预分配

  1. slice预分配内存:make初始化的时候指定大小 2. 减少slice扩容产生的拷贝
  2. map一样也需要预分配

字符串处理:三种方式

  1. 直接使用“+”
  2. 使用:string.Builder
  3. 使用:bytes.Buffer
func Plus(n int, str string) string {
    s := "" 
    for i := 0; i < n; i++ {
        s += str 
    } 
    return s 
} 
func StrBuilder(n int, str string) string {
    var builder strings.Builder 
    for i := 0; i < n; i++ {
        builder.WriteString(str)
    } 
    return builder.String() 
} 
func ByteBuffer(n int, str string) string {
    buf := new(bytes.Buffer) 
    for i := 0; i < n; i++ {
        buf.WriteString(str) 
    } return buf.String() 
}
  1. 性能:strings.Buffer > bytes.Buffer >> +
  2. 字符串时不可变类型,占用的内存大小是固定的
  3. 每次使用+都会重新给字符串分配内存,大小为两个字符串长度之和
  4. strings.Buffer 、bytes.Buffer底层都是[]byte数组,所以有扩容策略,并不需要每次都重新分配内存
  5. bytes.Buffer转化位字符串时重新申请了一块内存空间
  6. strings.Buffer直接将底层的[]byte转化成了string返回

空结构体

空结构体不占用内存空间,故而可用其传递信号

atomic包

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