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

45 阅读7分钟

何为高质量编程

编写的代码能够达到正确可靠、简洁清晰的目标可称之为高质量代码。需要注意以下几点:

各种边界条件是否考虑完备;

异常情况处理,稳定性保证;

易读易维护。

编程的原则应遵守简单性、可读性和高效的生产力。

如何编写高效的GO代码

1、推荐使用 gofmt 自动格式化代码;

2、合理使用注释:注释应该解释代码作用、如何做的、实现的原因及什么情况会出错。公共符号始终要注释;

3、命名规范:

(1)variable:简洁胜于冗长;缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写;变量距离其被使用的地方越远,则需要携带越多的上下文信息。

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

(3)package:只由小写字母组成;不包含大写字母和下划线等字符简短并包含一定的上下文信息,例如 schema、task 等;不要与标准库同名,例如不要使用 sync 或者 strings。

4、控制流程:避免嵌套,保持正常流程清晰;尽量保持正常代码路径为最小缩进;尽量保持正常代码路径为最小缩进。

5、错误和异常处理:

(1)简单错误:优先使用 errors.New 来创建匿名变量来直接表示简单错误;如果有格式化的需求,使用 fmt.Errorf。

(2)错误的 Wrap 和 Unwrap:错误的 Wrap 实际上是提供了一个 error 嵌套另一个error的能力,从而生成一个 error 的跟踪链;在 fmt.Errorf 中使用: %w 关键字来将一个错误关联至错误链中。

(3)错误判定:判定一个错误是否为特定错误,使用 errors.Is;不同于使用 ==,使用该方法可以判定错误链上的所有错误是否含有特定的错误;在错误链上获取特定种类的错误,使用errors.As

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

(5)recover:recover 只能在被 defer 的函数中使用;嵌套无法生效;只在当前 goroutine 生效;defer 的语句是后进先出;如果需要更多的上下文信息,可以 recover 后在 og 中记录当前的调用栈。

性能优化建议

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

go test -bench=. -benchmem

Slice

(1)slice预分配内存

尽可能在使用 make() 初始化切片时提供容量信息:

func PreAlloc( size int) {

data := make([]int, 0, size)

for k := 0; k < size; k++ {

data = append(data, k)

}

切片本质是一个数组片段的描述: 包括数组指针; 片段的长度; 片段的容量(不改变内存分配情况下的最大长度);

切片操作并不复制切片指向的元素;

创建一个新的切片会复用原来切片的底层数组。

(2) 大内存未释放

在已有切片基础上创建切片,不会创建新的底层数组。

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

可使用 copy 替代 re-slice。

Map

map预分配内存

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

不断向 map 中添加元素的操作会触发 map 的扩容;

提前分配好空间可以减少内存拷贝和 Rehash 的消耗;

建议根据实际需求提前预估好需要的空间。

字符串处理

字符串拼接:使用 + 拼接性能最差,strings.Builderbytes.Buffer 相近,strings.Buffer 更快。

func StrBuilder(n int, str string) string {

//strings.Builder 直接将底层的 []byte 转换成了字符串类型返回

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

//bytes.Buffer 转化为字符串时重新申请了一块空间

    buf := new(bytes.Buffer)
    
    for i := 0;i < n; i++ {
    
        buf.writeString(str)
        
    }
        
    return buf.String()
    
}

字符串在 Go 语言中是不可变类型,占用内存大小是固定的;

使用 + 每次都会重新分配内存;

strings.Builder,bytes.Buffer 底层都是[]byte;

数组内存扩容策略,不需要每次拼接重新分配内存.

字符串处理

使用空结构体节省内存,可作为各种场景下的占位符使用

func EmptyStructMap(n int) {

    m := make(map[int]structi{})
    
    for i := 0;i < n; i++ {
        m[i] = structf{}{}
    }
}

func BoolMap(n int) {

    m := make(map[int]bool)
    
    for i := 0;i < n; i++ {
        m[i] = false
    }
}

atomic包

如何使用atomic包

type atomicCounter struct {
    i int32
}

func AtomicAddOne(c
*atomicCounter) {
    atommic.AddInt(&c.i, l)
}


type mutexCounter struct {
    i int32
    m sync.Mutex
}

func MutexAddOne(c *mutexCounter) {
    c.m.lock()
    c.i++
    c.m.Unlock()
}

性能调优实战

性能分析工具pprof的使用

图片优化、前端资源优化、数据请求优化等,通过实战和测试,分析和优化任意项目中存在的性能问题,在Go语言中,高质量编程和性能调优是开发人员必须重视的关键方面。以下是一些关于Go语言高质量编程和性能调优的技巧和建议,包括图片优化、前端资源优化和数据请求优化。

图片优化

  1. 选择适当的图片格式:对于图形图片,使用无损的PNG格式,对于照片,使用有损的JPEG格式。这样可以在保持图片质量的同时减小文件大小。

  2. 压缩图片:使用图像处理工具压缩图片,减小文件大小。可以使用开源库,如Imaging、gmagick或GraphicsMagick等进行图片压缩。

  3. 使用CDN:将图片资源托管到CDN上,确保图片可以快速加载并提供最佳的用户体验。

前端资源优化

  1. 压缩和合并CSS和JavaScript文件:使用工具如HTMLMinifier等压缩和合并CSS和JavaScript文件,以减小文件大小并减少网络请求的数量。

  2. 使用浏览器缓存:设置合适的缓存头部,使浏览器可以缓存静态资源,减少网络请求。

  3. 懒加载:只在需要时加载资源,可以加快页面加载速度。常见的懒加载库有LazyLoad、Echo.js等。

  4. 使用雪碧图:将多个小图标合并为一个雪碧图,减少请求次数。

数据请求优化

  1. 使用并发请求:使用Go语言的goroutine和channel机制来实现并发请求,提高系统的并发能力和响应速度。

  2. 批量处理数据:将多个相关的数据请求合并为一个请求,减小网络开销,提高性能。

  3. 缓存数据:使用合适的缓存策略和缓存库,如Redis或Memcached等,缓存一些频繁访问的数据,提高数据读取速度。

  4. 优化数据库查询:使用索引优化数据库查询,合理设计数据库表结构,减小数据库访问的开销。

总之,Go语言高质量编程和性能调优需要开发者深入了解应用的运行情况和性能瓶颈,并灵活运用相关的优化技术和工具。通过图片优化、前端资源优化和数据请求优化等措施,我们可以提升应用的性能和响应速度,提高用户体验。