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

203 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记

1、注释规范
  • 注释可以帮我们很好的完成文档的工作,写得好的注释可以方便我们以后的维护。
  • /**/ 的块注释和 // 的单行注释两种注释风格, 在项目中为了风格的统一,全部使用单行注释,注释的质量决定了生成的文档的质量。
  • 统一使用中文注释,对于中英文字符之间严格使用空格分隔, 这个不仅仅是中文和英文之间,英文和中文标点之间也都要使用空格分隔
  • 全部使用单行注释,禁止使用多行注释
  • 和代码的规范一样,单行注释不要过长,禁止超过 120 字符。
1.1、包注释
  • 每个包都应该有一个包注释,一个位于 package 子句之前行注释
  • 包注释应该包含下面基本信息
// @Title  请填写文件名称(需要改)
// @Description  请填写文件描述(需要改)
// @Author  请填写自己的真是姓名(需要改)  ${DATE} ${TIME}
// @Update  请填写自己的真是姓名(需要改)  ${DATE} ${TIME}
package ${GO_PACKAGE_NAME}
1.2、结构(接口)注释
// User   用户对象,定义了用户的基础信息
type User struct{
    Username  string // 用户名
    Email     string // 邮箱
}
1.3、函数注释
  • 每个函数,或者方法(结构体或者接口下的函数称为方法)都应该有注释说明
  • 函数的注释应该包括三个方面
// @title    函数名称
// @description   函数的详细描述
// @auth      作者             时间(2019/6/18   10:57 )
// @param     输入参数名        参数类型         "解释"
// @return    返回参数名        参数类型         "解释"
1.4、代码逻辑注释
// TODO  代码块的执行解释
if   userAge < 18 {
​
}
2、命名规范
2.1、包名
  • 当命名包时,请按下面规则选择一个名称:

    • 全部小写。没有大写或下划线。
    • 大多数使用命名导入的情况下,不需要重命名。
    • 简短而简洁。请记住,在每个使用的地方都完整标识了该名称。
    • 不用复数。例如 net/url,而不是 net/urls
    • 不要用 “common”“util”“shared”“lib”。这些是不好的,信息量不足的名称。
2.2、函数名
  • 使用驼峰命名
  • 如果包外不需要访问请用小写开头的函数
  • 如果需要暴露出去给包外访问需要使用大写开头的函数名称
  • 尽量体现函数作用
2.3、导入别名
  • 如果程序包名称与导入路径的最后一个元素不匹配,则必须使用导入别名。
  • 在所有其他情况下,除非导入之间有直接冲突,否则应避免导入别名。
// bad
import (
  "fmt"
  "os"
​
  nettrace "golang.net/x/trace"
)
​
// good
import (
  "fmt"
  "os"
  "runtime/trace"
​
  nettrace "golang.net/x/trace"
)
3、代码规范
3.1、指向 Interface 的指针
  • interface 作为值传递的时候,底层数据就是指针。

  • Interface 包括两方面:

    • 一个包含 type 信息的指针
    • 一个指向数据的指针
  • 如果想要修改底层的数据,那么只能使用 unsafe.pointer

3.2、零值 Mutex
  • 零值 sync.Mutexsync.RWMutex 是有效的。所以指向 mutex 的指针基本是不必要的
// bad
mu := new(sync.Mutex)
mu.Lock()
​
//good
var mu sync.Mutex
mu.Lock()
3.3、接收 Slices 和 Maps
  • 当 map 或 slice 作为函数参数传入时,如果存储了对它们的引用,则用户可以对其进行修改
// bad
func (d *Driver) SetTrips(trips []Trip) {
  d.trips = trips
}
​
// good
func (d *Driver) SetTrips(trips []Trip) {
  d.trips = make([]Trip, len(trips))
  copy(d.trips, trips)
}
​
3.4、channel 的 size 最好是 1 或者是 unbuffered
  • 在使用 channel 的时候,最好将 size 设置为 1 或者使用 unbuffered channel。
  • 其他 size 的 channel 往往都会引入更多的复杂度,需要更多考虑上下游的设计
// bad
c := make(chan int, 64) // 应该足以满足任何情况// good
c := make(chan int, 1) // 或者
c := make(chan int)
3.5、错误类型
  • Go 中有多种声明错误(Error) 的选项:

    • errors.New 对于简单静态字符串的错误
    • fmt.Errorf 用于格式化的错误字符串
    • 实现 Error() 方法的自定义类型
    • "pkg/errors".WrapWrapped errors
  • 返回错误时,请考虑以下因素以确定最佳选择:

    • 如果这是一个不需要额外信息的简单错误,使用errors.New
    • 如果客户需要检测并处理此错误,则应使用自定义类型并实现该 Error() 方法。
    type errNotFound struct {
        file    string
    }
    ​
    func (e errNotFound) Error() string {
        return fmt.Sprintf("file %q not found", e.file)
    }
    ​
    func Open(file string) error {
      return errNotFound{file: file}
    }
    ​
    func use() {
      if err := open(); err != nil {
        if _, ok := err.(errNotFound); ok {
          // handle
        } 
          ...
      }
    }
    
    • 如果正在传播下游函数返回的错误,使用错误包装
    • 如果客户端需要检测错误,并且已使用创建了一个简单的错误 errors.New,请使用一个错误变量。
    • 其他错误 fmt.Errorf就可以了。
3.6、错误包装 (Error Wrapping)
  • 一个(函数 / 方法)调用失败时,有三种主要的错误传播方式:

    • 如果没有要添加的其他上下文,并且想要维护原始错误类型,则返回原始错误。
    • 添加上下文,使用 "pkg/errors".Wrap 以便错误消息提供更多上下文,"pkg/errors".Cause 可用于提取原始错误。
    • 如果调用者不需要检测或处理的特定错误情况,使用 fmt.Errorf
  • 在将上下文添加到返回的错误时,请避免使用 “failed to” 之类的短语来保持上下文简洁,这些短语会陈述明显的内容,并随着错误在堆栈中的渗透而逐渐堆积
// bad
fmt.Errorf("failed to create new store: %s", err)
​
// good
fmt.Errorf("new store: %s", err)
3.7、类型转换失败处理
  • 类型转换失败会导致进程 panic,所以对于类型转换,一定要使用 !ok 的范式来处理
// bad
t := i.(string)
​
// good
t, ok := i.(string)
if !ok {
  // 优雅地处理错误
}
3.8、避免字符串到字节的转换
  • 不要反复从固定字符串创建字节 slice。相反,请执行一次转换并捕获结果
// bad
for i := 0; i < b.N; i++ {
  w.Write([]byte("Hello world"))
}
​
// good
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
  w.Write(data)
}
3.9、编程风格一致性
  • 一致性的代码更容易维护、是更合理的、需要更少的学习成本、并且随着新的约定出现或者出现错误后更容易迁移、更新、修复 bug
  • 相反,一个单一的代码库会导致维护成本开销、不确定性和认知偏差。所有这些都会直接导致速度降低、 代码审查痛苦、而且增加 bug 数量
  • 将这些标准应用于代码库时,建议在 package(或更大)级别进行更改,子包级别的应用程序通过将多个样式引入到同一代码中,违反了上述关注点
// bad
type Area float64
type Volume float64//good
type (
  Area float64
  Volume float64
)
3.10、减少嵌套
  • 代码应通过尽可能先处理错误情况 / 特殊情况并尽早返回或继续循环来减少嵌套。减少嵌套多个级别的代码的代码量。
// bad
for _, v := range data {
  if v.F1 == 1 {
    v = process(v)
    if err := v.Call(); err == nil {
      v.Send()
    } else {
      return err
    }
  } else {
    log.Printf("Invalid v: %v", v)
  }
}
​
// good
for _, v := range data {
  if v.F1 != 1 {
    log.Printf("Invalid v: %v", v)
    continue
  }
​
  v = process(v)
  if err := v.Call(); err != nil {
    return err
  }
  v.Send()
}
参考链接