这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
1 高质量编程
1.1 简介
编写的代码能够达到正确可靠、简洁清晰的目标可称之为高质量代码。应该能够考虑到各种边界条件;对异常情况进行正确处理,保证稳定性;代码易读易维护。实际应用场景千变万化,各种语言的特性和语法各不相同,但是高质量编程遵循的原则是相同的,如简单性、可读性、生产力。
1.2 编码规范
- 代码格式:使用gofmt自动化格式代码为官方统一风格;使用goimports自动对依赖包进行管理
- 注释:
- 解释代码的作用(适合注释公共符号)
- 解释代码如何实现(适合注释实现过程)
- 解释代码实现的原因(适合解释代码的外部因素、提供额外上下文)
- 解释代码什么情况会出错(适合解释代码的限制条件)
- 公共符号始终要注释
- 包中声明的每个公共符号:变量、常量、函数以及结构体都需要添加注释
- 任何既不明显也不简短的公共功能必须予以注释
- 无论长度或复杂程度如何,对库中的任何函数都必须进行注释
- 命名规范
- 变量:变量名要简洁清晰。缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写;变量距离其被使用的地方越远,则需要携带越多的上下文
- 函数:函数名应尽量简短,不携带包名的上下文信息,因为包名和函数名总是成对出现的
- 包:包名应简短并包含一定的上下文信息,只由小写字母组成,不包含大写字母和下划线等字符,不与标准库同名;尽量避免使用常用变量名作为包名;尽量使用单数而不是负数;谨慎地使用缩写
- 控制流程:避免嵌套,保证正常流程清晰;优先处理错误情况、特殊情况,尽早返回或继续循环来减少嵌套,保持正常代码路径为最小缩进
- 错误和异常处理
- 对于仅出现一次,且在其他地方不需要捕获的简单错误,优先使用
errors.New来创建匿名变量直接表示,如有格式化需求,使用fmt.Errorf - 使用
errors.Is来判定一个错误是否为特定错误,使用errors.As获取特定种类的错误 - 不建议在业务代码中使用
panic,当调用函数不包含recover时,会造成程序崩溃,因此若问题可以被屏蔽或解决,建议使用error代替panic recover只能在defer的函数中被使用,嵌套无法生效,只能在当前goroutine生效,defer的语句是后进先出
- 对于仅出现一次,且在其他地方不需要捕获的简单错误,优先使用
2 性能优化
性能优化的前提是满足正确可靠、简洁清晰等质量因素,性能优化是综合评估,有时候时间效率和空间效率可能对立。
2.1优化建议
- slice预分配内存:尽可能在使用make()初始化切片时提供容量信息。
- 因为切片的底层引用了一个数组,当添加元素之后的长度小于容量时,会直接利用原数组的剩余空间。否则,会分配一块更大的区域来容纳新的底层数组。因此,预先设置容量,能够避免额外的内存分配
- 大内存未释放。在已有切片基础上创建切片,不会创建新的底层数组。如果原切片由大量元素组成,在原切片基础上新建小切片后,虽然只使用了很少元素,但底层数组在内存中有引用,得不到释放,可使用
copy来代替re-slice
- map预分配内存:不断向map添加元素会触发扩容,提前分配好空间可以减少内存拷贝和Rehash的消耗
- 使用
strings.Builder拼接字符串:字符串在Go语言中是不可变类型,占用内存大小是固定的,使用+每次都会重新分配内存,而strings.Builder和strings.Buffer底层都是[]byte数组,不需要每次拼接都重新分配内存。Buffer转化为字符串时重新申请了一块空间,Builder直接将底层的[]byte转换成了字符串类型返回,因此Builder会比Buffer快一些。 - 使用空结构体节省内存:空结构体
struct{}实例不占据任何的内存空间,可作为各种场景下的占位符使用。如使用map实现set,由于只需用到map的键,而不需要值,所以可以使用空结构体作为值类型。即便将map的值设为bool类型,也会多占据1个字节空间。 - 使用
atomic包:锁的实现是通过操作系统来实现,属于系统调用,而atomic操作是通过硬件实现的,效率比锁高。