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

一、高质量编程

什么是高质量?

  • 各种边界条件是否考虑完备
  • 异常情况处理,稳定性保证
  • 易读易维护

编码规范

1.代码格式

推荐使用gofmt自动格式化代码,gofmt能够自动格式化go语言代码为官方统一风格。

2.注释---写好代码是前提

注释应该写什么?

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

包中声明的每个公共符号,包括变量、常量、函数以及结构都需要添加注释。

3.命名规范---最容易且必须做到

变量命名规则

第一个单词小写,后面驼峰

ex: var userDao UserDao

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

    • 例如使用ServeHTTP而不是ServeHttp
    • 使用XMLHTTPRequest或者xmlHTTPRequest
  • 变量距离其被使用的地方越远,则需要携带越多的上下文信息,即越详细
  • 全局变量在其名字中需要更多的上下文信息,使得在不同地方可以轻易辨认出其含义

函数命名规则

一般为驼峰式(非导出函数例外)

ex: func QueryUserById()

  • 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的
  • 函数名尽量简短
  • 当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义
  • 当名为foo的包某个函数返回类型T时(T 并不是Foo),可以在函数名中加入类型信息

包命名规则

全小写!!!

  • 只由小写字母组成。不包含大写字母和下划线等字符
  • 简短并包含一定的上下文信息。例如schema、task 等
  • 不要与标准库同名。例如不要使用syne或者strings
结构体命名规则

名字和字段都采用驼峰式,非导出除外

type User struct {
    Id   int64
    Name string
    Age  int64
    CreateTime time.Time
}

4.控制流程

  • 避免嵌套(能少写代码就少写)

    if condition{
        retrun x
    }
    return y
  • 优先处理异常状况

    if err!=nil{
        
    }

5.错误和异常处理

  • 少用panic(),除非真正异常的情况
  • 多用errors.New()fmt.Errof()
错误链操作
  • 判断一个错误是否为特定错误errors.Is(errs,targetError)
  • 从错误链上获取特定错误errors.As(errs,&targetError)

二、性能优化

1.性能优化指南

如何评价?

举个栗子~

//基准测试函数
func BenchmarkFab10(b *testing.B) {
    for i := 0; i < b.N; i++ {
        //计算斐波拉契数列
        Fab(10)
    }
}

执行命令go test -bench="." -benchmem 特别注意引号

-bench 参数支持传入一个正则表达式,匹配到的用例才会得到执行

image-20230117183234579

依次表示:

  • GOMAXPROCS 默认为CPU核数
  • 即b.N的值
  • 每次执行花费的时间
  • 每次执行申请多大的内存
  • 每次执行申请几次内存

性能优化建议

1.slice与map 预分配内存

NoPreAlloc

func NoPreAlloc(size int) {
    data := make([]int, 0)
    //data := make(map[string]int) 
    for i := 0; i < size; i++ {
        data = append(data, i)
    }
}

PreAlloc

func PreAlloc(size int) {
    data := make([]int, size)
    //data := make(map[string]int) 
    for i := 0; i < size; i++ {
        data = append(data, i)
    }
}

性能差异

image-20230117214240562

原理

image-20230117214431342

2.slice 大内存未释放

切片是引用,如果我们在一个大的切片上构造一个小的切片,那么大的切片仍在内存之中不会释放。

解决方法:使用copy函数

func ByCopy(origin []int) []int {
    result := make([]int, 2)
    copy(result, origin[len(origin)-2:])
    return result
}
3.字符串处理

字符串拼接推荐使用strings.Builder ,bytes.Buffer性能更差

func StrBuilder(n int, str string) string {
    var builder strings.Builder
    for i := 0; i < n; i++ {
        builder.WriteString(str)
    }
    return builder.String()
}

image-20230117222739638

image-20230117222753687

原理

字符串在Go语言中是不可变类型,占用内存大小是固定的。使用+每次都会重新分配内存。strings.Builder, bytes. Buffer底层都是[]byte 数组,有相应的内存扩容策略,不需要每次拼接重新分配内存。

4.空结构体

实现Set,可以考虑用map代替。

因为空结构体struct{}实例不占据任何的内存空间,节省空间。

set := make(map[string]struct{})

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

2.性能分析工具 pprof

pprof是用于可视化和分析性能、数据的工具

image-20230118090219701

案例测试

运行测试项目:https://github.com/wolfogre/go-pprof-practice

因为项目引入了net/http/pprof注入的入口,所以打开http://localhost:6060/debug/pprof,该页面上展示了可用的程序运行采样数据

image-20230118091340016

  • CPU

go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"

top 查看占用资源最多的函数

image-20230119082258237

list 根据指定的正则表达式查找代码行

  • Heap

web界面更方便:

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"

​ source视图类似于之前的list命令

​ 采样数据:

image-20230119084742578

  • goroutine

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"

  • mutex

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"

  • block

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"

最后修改:2023 年 06 月 07 日
如果觉得我的文章对你有用,请随意赞赏