这是我参与「第五届青训营」伴学笔记创作活动的第 3天
1 高质量编程
1、高质量
高质量:正确可靠、简洁清晰
边界条件、异常处理、易读易维护
编程原则:简单、可读、生产力
2、编码规范
-
代码格式
gofmt自动格式化代码
-
注释
作用、实现过程、实现原因、什么情况会出错
变量、常量、函数、结构
-
命名规范
-
变量:缩略词全大写,但位于变量开头且不需要导出是全小写
-
函数:函数名不携带包名的上下文信息;尽量简短;返回类型T不是Foo时,可以加入类型信息
http包中: 1. func Serve(I net.Listener, handler Handler) err 2. func ServeHTTP(I net.Listener, handler Handler) err http.serve √ heep.serverHTTP × -
包:小写,不含大写和下划线等;简短且包含一定的上下文信息;不要与标准库同名
-
-
控制流程
避免复杂嵌套
-
错误和异常处理
- 简单错误:errors.New、fmt.Errorf
- 错误的Wrap Unwrap
- 错误判定
- errors.Is:判定一个错误是否为特定错误
- errors.As:在错误链上获取指定种类的错误
- panic
- 不建议使用
- 调用函数不包含recover会造成程序崩溃
- 若问题可以被屏蔽或解决,建议使用error或panic替代
- 当程序在启动阶段发生不可逆转的错误时,可以在init或main函数中使用panic
- recover
- 只能在defer的函数中使用
- 嵌套无法生效
- 只在当前goroutine生效
- defer的语句是后进先出的
- 如果需要等多的上下文信息,可以在recover后再在og中记录当前的调用栈
- defer语句会在函数返回前调用,多个defer语句是后进先出的
2 性能调优实战
1、Benchmark
性能需要实际数据来衡量,可以使用奔驰玛瑞克工具进行基准性能测试
go test -bench=. -benchmem
2、Slice
-
Slice预分配内存:尽可能使用make()初始化切片时提供容量信息
data := make([]int, 0, size) -
切片本质是一个数组片段的描述,包括数组指针、片段长度、片段容量(不改变内存分配情况下的最大长度)
type slice struct { array unsafe.Pointer len int cap int } -
切片操作并不复制切片指向的元素
-
创建一个新的切片会复用原来切片的底层数组
-
大内存释放:在已有切片的基础上创建切片,不会创建新的底层数组
- 应用场景:原切片较大,代码在原切片基础上新建小切片;底层数组在内存中有引用,得不到释放
- 可使用copy代替reslice
go test -run=. -v
3、Map
- map预分配内存
- 不断向map中添加元素的操作会触发map的扩容
- 提前分配好空间可以减少内存拷贝和Rehash的消耗
- 根据实际需求提前预估好需要的空间
4、字符串处理
-
使用strings.Builder
-
使用+拼接性能很差,strings.Builder,bytes.Buffer相近,strings.Buffer更快
- 字符串在Go语言中是不可变类型,占用内存大小是固定的,使用+每次都会重新分配内存
- strings.Builder bytes.Buffer底层都是[]byte数组,内存扩充策略,不需要每次拼接重新分配内存
-
bytes.Buffer转化为字符串时重新申请了一块空间
-
strings.Builder直接将底层的[]byte转换成了字符串类型返回
func (b *Buffer) String() string { if b == nil { return "<nil>" } return string(b.buf[b.off:]) } func (b *Builder) String() string { return *(*string)(unsafe.Pointer(&b.buf)) }
-
5、空结构体
使用空结构体节省内存
-
空结构体
struct{}实例不占据任何的内存空间,可以作为各种场景下的占位符使用- 节省资源;语义强,仅作为占位符
-
实现Set,可以考虑用map来代替
- 只需要用到map的键,而不需要值
6、atomic包
锁的实现是通过操作系统来实现的,属于系统调用
atomic操作是通过硬件实现,效率比锁高
sync.Mutex用来保护一段逻辑,不仅仅保护一个变量
对于非数值操作,可使用atomic.Value,能承载一个interface{}