Go学习笔记之高质量编程 | 青训营

72 阅读4分钟

(一)编程原则

对于高质量编程,以下三点原则非常重要:

  1. 简单性:应该用简单清晰的逻辑编写代码
  2. 可读性:“代码是写给人看的,而不是机器”
  3. 生产力:注重编程的团队合作

(二)编程规范

代码格式

推荐使用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){
    
}