(一)编程原则
对于高质量编程,以下三点原则非常重要:
- 简单性:应该用简单清晰的逻辑编写代码
- 可读性:“代码是写给人看的,而不是机器”
- 生产力:注重编程的团队合作
(二)编程规范
代码格式
推荐使用gofmt自动格式化代码。
gofmt是由Go官方提供的工具。能自动格式化Go语言代码为官方统一风格。
注释
1. 什么需要注释:
公共符号始终要注释
变量、常量、函数以及结构
任何既不明显也不简短的公共功能都必须要注释
无论长度和复杂程度如何,对库中的任何函数都必须进行注释。
(这里要注意我们不需要注释实现接口的方法)
2. 注释用于解释什么:
解释代码是如何做的:注释实现过程
解释代码实现的原因:适合解释代码的外部因素,提供额外上下文
解释代码什么情况下会出错
命名规范
1. 变量名:
· 简洁胜于冗长
· 缩略词全大写,但是当它位于变量开头并且不需要导出给外部使用时,使用全小写
· 变量距离其被使用的地方越远,则需要携带越多的上下文信息
2. 函数名:
· 不携带包名的上下文信息,因为包名和函数名总是成对出现
· 尽量简短
· 当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义;
· 当名为foo的包某个函数返回类型T时,可以在函数名中加入类型信息
3. 包名:
· 只用小写字母
· 使用单数而不是复数
· 简短且包含一定的上下文信息
· 不与标准库同名
控制流程
主要注意避免嵌套
要尽量保持正常代码路径为最小缩进:优先处理错误情况/特殊情况,尽早返回
错误和异常处理
1. 简单错误:
· 指仅出现一次且在其他地方不需要捕获该错误
· 优先使用errors.New来创建匿名变量直接表示简单错误
· 如果有格式化的需求,使用fmt.Errorf
func defaultCheckRedirect(req *Request, via []*Request) error{
if len(via) >= 10 {
return errors.New("Stopped after 10 redirects")
}
return nil
}
2. 错误的Wrap和Unwrap
· 错误的Wrap实际上是提供了一个error嵌套另一个error的能力,从而生成一个error跟踪链,以提供关于错误的额外的上下文
· 在fmt.Errorf中使用:%w关键字来将一个错误关联至错误链中
3. 错误判定
· 判定一个错误是否为特定错误,使用errors.Is
· 不同于使用==,使用该方法可以判定错误链上的所有错误是否含有特定的错误
· 在错误链上获取特定种类的错误,用As
· 不建议在业务代码中使用panic,除非在程序启动阶段发生不可逆转的错误
4. recover:宕机恢复
可以让进入宕机流程中的goroutine中恢复过来,recover仅在延迟函数defer中有效
性能调优
1. 内存预分配
(1)slice
尽可能在使用make()初始化切片时提供容量信息
data := make([]int, 0, n)
切片:本质上是一个数组片段的描述,包括数组指针、片段长度、片段容量
切片操作并不复制切片指向的元素,不会创建新的底层数组
=》在原切片基础上新建小切片,导致原底层数组在内存中有引用,得不到释放
=》可以用copy代替re-slice
func GetLastBySlice(origin []int) []int {
return origin[len(origin)-2:]
}
func GetLastByCopy(origin []int) []int {
result := make([]int, 2)
copy(result, origin[len(origin)-2:])
return result
}
(2)map
data := make(map[int]int, n)
2. 字符串处理
(1)使用strings.Builder拼接
func StrBuilder(n int, str string) string{
var builder strings.Builder
builder.Grow(n * len(str))//预知长度的话可以进行内存预分配,提高速度
for i := 0; i < n; i++{
builder.WriteString(str) // 对比 s := "" s += str
}
return builder.String()
}
(2)使用bytes.Buffer拼接
func ByteBuffer(n int, str string) string{
buf := new(bytes.Buffer)
buf.Grow(n * len(str))
for i := 0; i < n; i++{
buf.WriteString(str)
}
return buf.String()
}
两种方法进行对比:
· bytes.Buffer转化为字符串时重新申请了一块空间;
· strings.Builder直接将底层的[]byte转换成了字符串类型返回
3. 空结构体
可以用来节省内存:
空结构体struct{}实例不占据任何的内存空间,可作为各种场景下的占位符使用
func EmptyStructMap(n int){
}