这是我参与「第三届青训营 -后端场」笔记创作活动的的第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.Mutex和sync.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".Wrap的Wrapped 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()
}