- 一般情况下,interface 可以直接进行值传递,除非你需要修改 interface 指向的数据。interface 本身很轻量,其包括指向数据类型的指针和存储数据的指针。
- Value Receiver 方法可以通过值或者指针调用;Pointer Receiver 则只接受指针调用。换句话说,指针调用方法更加轻松,限制更少。
- Mutex 和 RWMutex 可以直接使用其零值,零值表示 Unlock 状态。已经被使用的 Mutex 不能被拷贝。
对于 Map & Slice,需要注意其作为参数和返回值的时候,可能会受到外界操作的影响。 安全的做法是在内部做一次拷贝,内部可安全使用。
1
2d.trips = make([]Trip, len(trips))
copy(d.trips, trips)Channel 的 size 要么是 1,要么是无缓冲的(这个还是要看情况吧)
- 常规情况下,枚举从 1 开始计数;除非 0 是有一定意义的。如:
LogToStdout = 0
直接导出自定义错误要小心,最好只公开错误匹配器,方便进行错误检查:
1
2
3
4
5
6
7
8
9
10
11
12type errNotFound struct {
file string
}
func (e errNotFound) Error() string {
return fmt.Sprintf("file %q not found", e.file)
}
func IsNotFoundError(err error) bool {
_, ok := err.(errNotFound)
return ok
}合理使用 Error Wrapping,不用添加过多冗余信息,如:
failed to : error message
。- 在生产环境中,运行的程序避免 panic,panic/recover 不是错误处理策略,而是当不可恢复的事情发生时,程序才必须 panic。
- 使用 go.uber.org/atomic 替代标准库
sync/atomic
,避免忘记使用原子操作。 import _
应该只用于main
文件中,尽可能靠近程序启动位置。
性能
- 优先使用
strconv
而非fmt
转换类型,性能更好。 - 不要反复(如循环中)从固定字符串创建字节 slice,反之亦然。
- map 创建时尽量提供容量 hint,这样可以避免在添加元素期间过多次地分配。map 虽然不能保证分配 hint 个容量,添加元素时依然可以进行分配,但它可以在运行时有更少的分配。
规范
- 保证一致性,便于代码的维护、减少学习成本
- 相似的声明放在一个分组(import/var/const/type)。仅将相关的声明放到一起!
- 包命名规则:
- 全部小写。无大写或下划线
- 多数使用命名导入的情况下,无需重命名包
- 简短 & 简洁
- 不要使用复数
- 函数分组与顺序:
- 函数粗略按照调用顺序排序
- 同一个文件中,函数应该按照接收者分组
- 导出的函数应该先出现在文件中,放在
var
,struct
,const
定义的后面
1 | type something struct{ ... } |
- 对于未导出的顶层常量和变量,使用
_
作为前缀。未导出的错误值,使用err
开头。原因:顶层变量和常量具有包范围作用域,使用通用名称可能会导致在其他文件中意外使用错误值。 - 结构体嵌入,多一行空行隔开
nil
是一个有效的 slice;零值切片(使用var
声明的切片)可立即使用,无需调用make()
创建。1
2
3
4
5
6
7var nums []int
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}如果要声明格式字符串,设置为
const
类型,有助于go vet
执行字符串静态检查:1
2const msg = "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)