这是我参与「第五届青训营」伴学笔记创作活动的第3天
编码规范
注释
- 公共符号、函数始终要注释
包中声明的每一个公共符号、变量、常量、函数以及结构体都需要注释
任何不明显也不简短的公共功能也需要给予注释
无论长度或复杂度如何,对库中的任何函数都必须进行注释
有一例外,不需要注释实现接口的方法
- 注释应该有的作用
应该解释代码作用
应该解释代码如何做的
应该解释代码实现的原因
应该解释代码为什么会出错
- 注释应该提供未表达出来的上下文信息
代码格式
推荐使用go fmt自动格式化代码
go imports:在go fmt的基础上加上了依赖包管理,自动增加删除依赖包应用、将依赖包按字母排序并分类
命名规范
变量命名
- 简洁胜于冗长
- 缩略词全部大写,但当位于变量头部并且不需要导出的时候,全部小写,比如使用ServerHTTP而不是ServerHttp
- 变量距离被使用的地方越远,则需要携带越多的上下文信息
函数命名
- 函数名不携带包的上下文信息,因为总是成对出现
- 函数名尽可能简短
- 当名为foo的包中的某个函数返回类型foo时,可以省略类型信息而不导致歧义,但返回类型为T时,则需要在函数名中加入类型信息
控制流程
- 避免嵌套,保持正常流程清晰
- 尽量保持正常代码路径为最小缩进
- 优先处理错误情况/特殊情况,尽早返回
- 处理逻辑尽量走直线
- 故障问题大多数出现在循环语句以及复杂的判断语句中
错误与异常处理
简单错误
- 简单错误是指只会出现一次的错误,并且在其他地方不需要捕获该错误
- 优先使用errors.New来创建匿名变量直接表示简单错误
- 如果有格式化的要求,使用fmt.Errorf
错误的Wrap和Unwarp
- 错误的Wrap实际上是提供一个error嵌套另一个error的能力,从而能够跟踪错误链
- 在fmt.Errorf中使用%w将一个错误关联到错误链中
错误判定
- 判定一个错误是不是特定错误,使用errors.ls
- 不同于使用==,使用该方法可以判定错误链上的所有错误是否含有特定错误
- 在错误链上获取特定错误内容使用errors.As
panic
- 议在业务中使用panic
- 调用函数不包含recovery会造成程序崩溃
- 若问题可以被屏蔽或者解决,建议使用error代替panic
- 序启动的初始阶段出现不可逆转的错误时,可以在init或者main函数中使用panic
recover
- cover只能在被defer的函数中使用
- 嵌套无法生效
- 只能在当前的协程中使用
- defer语句是后进先出
- 如果需要更多的上下文信息,可以在recover后log中记录当前的调用栈
性能优化建议
-
性能表现需要数据说明
-
go中提供了支持基准性能测试的benchmark工具,使用命令:go test -bench=. -benchmem
slice预分配内存
尽可能使用make()初始化切片的时候提供容量信息
- 切片本质上是一个数组片段的描述
- 包括数组指针
- 片段的长度
- 片段的容量
- 切片操作并不复制切片指向的元素
- 创建一个新的切片会复用原来切片的底层数组
大内存没有释放陷阱
在已有切片的基础上创建切片不会创建新的切片数组
原切片较大,代码在原切片的基础上新建小切片,底层数组在内存中有引用,得不到释放,因此我们可以使用copy代替re-slice
map预分配内存
- 不断向map中添加元素会触发map扩容
- 提前分配好空间可以减少内存拷贝和Rehash的消耗
- 建议根据实际情况提前预估好分配的空间
strings.Builder
使用strings.Builder拼接字符串会减少性能消耗,因为string类型是不可变的,每次使用+都会重新分配内存
使用空结构体节省内存
空结构体并不会占据任何的内存空间,可以作为占位符使用,并且可以节省资源
在map中使用这个特点,可以用来实现set,因为只需要key不需要value,会更加节省内存
使用atomic包
使用atomic包可以提高性能