Go Advice
文章目录
Go-advice 中文版本
Go 箴言
- 不要通过共享内存进行通信,通过通信共享内存
- 并发不是并行
- 通道编排;互斥体序列化
- 接口越大,抽象就越弱
- 使零值有用
interface{}什么也没说- Gofmt 的风格不是人们最喜欢的,但 gofmt 是每个人的最爱
- 一点点复制比一点点依赖更好
- 系统调用必须始终使用构建标记进行保护
- 必须始终使用构建标记保护 Cgo
- Cgo 不是 Go
- 对于不安全的 package,没有任何保证
- 清楚比聪明更好
- 反射永远不清晰
- 错误就是价值观
- 不要只检查错误,还要优雅地处理它们
- 设计架构,命名组件,记录细节
- 文档是供用户使用的
- 不要恐慌
Author: Rob Pike See more: https://go-proverbs.github.io/
Go 之禅
- 每个 package 实现单一的目的
- 显式处理错误
- 尽早返回,而不是使用深嵌套
- 让调用者选择并发
- 在启动一个 goroutine 时,需要知道何时它会停止
- 避免 package 级别的状态
- 简单很重要
- 编写测试以锁定 package API 的行为
- 如果你觉得慢,先编写 benchmark 来证明
- 节制是一种美德
- 可维护性
Author: Dave Cheney See more: https://the-zen-of-go.netlify.com/
代码
Always go fmt your code
- 使用
go fmt/gofmt格式化你的代码, 让大家都开心 - 多个 if 语句可以折叠成 switch
- 用
chan struct{}来传递信号,chan bool表达的不够清楚 -
30 * time.Second比time.Duration(30) * time.Second更好 - 用
var foo time.Duration代替var fooMillis int64会更好 - 总是把 for-select 换成一个函数
- 分组定义
const类型声明和var逻辑类型声明
使用 go fmt 格式化
社区使用官方的 Go 格式,不要重新发明轮子。 尝试减少代码复杂度。 这将帮助所有人使代码易于阅读。
多个 if 语句可以折叠成 switch
|
|
用 chan struct{} 来传递信号, chan bool 表达的不够清楚
当你在结构中看到 chan bool 的定义时,有时不容易理解如何使用该值,例如:
|
|
但是我们可以将其改为明确的 chan struct {} 来使其更清楚:我们不在乎值(它始终是 struct {}),我们关心可能发生的事件,例如:
|
|
30 * time.Second 比 time.Duration(30) * time.Second 更好
你不需要将无类型的 const 包装在类型中,编译器会找出来。最好将 const 移到第一位:
|
|
用 time.Duration 代替 int64 + 变量名
|
|
按类型分组 const 声明,按逻辑和/或类型分组 var
|
|
这个模式也适用于 var。
- 每个阻塞或者 IO 函数操作应该是可取消的或者至少是可超时的
- 为整型常量值实现
Stringer接口 - 用 defer 来检查你的错误
|
|
- 任何 panic 都不要使用
checkErr函数或者用os.Exit - 仅仅在很特殊情况下才使用 panic, 你必须要去处理 error
- 不要给枚举使用别名,因为这打破了类型安全
|
|
-
如果你想省略返回参数,你最好表示出来
_ = f()比f()更好
-
我们用
a := []T{}来简单初始化 slice -
用 range 循环来进行数组或 slice 的迭代
for _, c := range a[3:7] {...}比for i := 3; i < 7; i++ {...}更好
-
多行字符串用反引号(`)
-
用
_来跳过不用的参数
|
|
- 如果你要比较时间戳,请使用
time.Before或time.After,不要使用time.Sub来获得 duration (持续时间),然后检查它的值。 - 总是将上下文作为第一个参数传递给具有
ctx名称的 func - 几个相同类型的参数定义可以用简短的方式来进行
|
|
|
|
- 一个 slice 的零值是 nil
|
|
|
|
- 不要将枚举类型与
<,>,<=和>=进行比较- 使用确定的值,不要像下面这样做:
|
|
- 用
%+v来打印数据的比较全的信息 - 注意空结构
struct{}, 看 issue: https://github.com/golang/go/issues/23440
|
|
-
包装错误: http://github.com/pkg/errors
- 例如:
errors.Wrap(err, "additional message to a given error")
- 例如:
-
在 Go 里面要小心使用
range:for i := range aandfor i, v := range &a,都不是a的副本- 但是
for i, v := range a里面的就是a的副本 - 更多: https://play.golang.org/p/4b181zkB1O
-
从 map 读取一个不存在的 key 将不会 panic
value := map["no_key"]将得到一个 0 值value, ok := map["no_key"]更好
-
不要使用原始参数进行文件操作
- 而不是一个八进制参数
os.MkdirAll(root, 0700) - 使用此类型的预定义常量
os.FileMode
- 而不是一个八进制参数
-
不要忘记为
iota指定一种类型
|
|
vs
|
|
不要在你不拥有的结构上使用 encoding/gob
在某些时候,结构可能会改变,而你可能会错过这一点。因此,这可能会导致很难找到 bug。
不要依赖于计算顺序,特别是在 return 语句中
|
|
为了防止 unkeyed 文字,添加 _ struct {} 字段
|
|
对于 Point {X:1,Y:1} 都可以,但是对于 Point {1,1} 则会出现编译错误:
|
|
使用 go vet 命令进行检查,提示没有足够的参数在所有结构中添加 _ struct {}。
为了防止结构比较,添加 func 类型的空字段
|
|
http.HandlerFunc 比 http.Handler 更好
用 http.HandlerFunc 你仅需要一个 func,http.Handler 需要一个类型。
移动 defer 到顶部
代码可读性更好和在函数结束时有什么被调用也更清楚了。
JavaScript 解析整数为浮点数并且你的 int64 可能溢出
用 json:"id,string" 代替
|
|
并发
- 以线程安全的方式创建一些东西的最好选择是
sync.Once- 不要用 flags, mutexes, channels or atomics
- 永远不要使用
select{}, 省略通道, 等待信号 - 不要在 channel 里关闭,这是它的创作者的责任。
- 往一个关闭的 channel 会引起 panic
-
math/rand中的func NewSource(seed int64) Source不是并发安全的,默认的lockedSource是并发安全的, see issue: https://github.com/golang/go/issues/3611 - 当你需要一个自定义类型的 atomic 值时,可以使用 atomic.Value
性能
- 不要省略
defer- 在大多数情况下 200ns 加速可以忽略不计
- 总是关闭 http body
defer r.Body.Close()- 除非你需要泄露 goroutine
- 过滤但不分配新内存
|
|
为了帮助编译器删除绑定检查,请参见此模式 _ = b [7]
-
time.Time有指针字段time.Location并且这对 go GC 不好- 只在大量的
time.Time才有意义,用 timestamp 代替
- 只在大量的
-
regexp.MustCompile比regexp.Compile更好- 在大多数情况下,你的正则表达式是不可变的,所以你最好在
func init中初始化它
- 在大多数情况下,你的正则表达式是不可变的,所以你最好在
- 请勿在你的热路径中过度使用
fmt.Sprintf. 由于维护接口的缓冲池和动态调度,它是很昂贵的。- 如果你正在使用
fmt.Sprintf("%s%s", var1, var2), 考虑使用简单的字符串连接。 - 如果你正在使用
fmt.Sprintf("%x", var), 考虑使用hex.EncodeToStringorstrconv.FormatInt(var, 16)
- 如果你正在使用
- 如果你不需要用它,可以考虑丢弃它,例如
io.Copy(ioutil.Discard, resp.Body)- HTTP 客户端的传输不会重用连接,直到body被读完和关闭。
|
|
- 不要在循环中使用 defer,否则会导致内存泄露
- ‘cause defers will grow your stack without the reason
- 不要忘记停止 ticker, 除非你需要泄露 channel
|
|
- 用自定义的 marshaler 去加速 marshaler 过程
- 但是在使用它之前要进行定制!例如:https://play.golang.org/p/SEm9Hvsi0r
|
|
-
sync.Map不是万能的,没有很强的理由就不要使用它。 -
在
sync.Pool中分配内存存储非指针数据 -
为了隐藏逃生分析的指针,你可以小心使用这个函数::
|
|
-
对于最快的原子交换,你可以使用这个
m := (*map[int]int)(atomic.LoadPointer(&ptr)) -
如果执行许多顺序读取或写入操作,请使用缓冲 I/O
- 减少系统调用次数
-
有 2 种方法清空一个 map:
- 重用 map 内存 (但是也要注意 m 的回收)
|
|
- 分配新的
|
|
模块
- 如果你想在 CI 中测试
go.mod(和go.sum)是否是最新 https://blog.urth.org/2019/08/13/testing-go-mod-tidiness-in-ci/
构建
- 用这个命令
go build -ldflags="-s -w" ...去掉你的二进制文件 - 拆分构建不同版本的简单方法
- 用
// +build integration并且运行他们go test -v --tags integration .
- 用
- 最小的 Go Docker 镜像
- https://twitter.com/bbrodriges/status/873414658178396160
CGO_ENABLED=0 go build -ldflags="-s -w" app.go && tar C app | docker import - myimage:latest
- run go format on CI and compare diff
- 这将确保一切都是生成的和承诺的
- 用最新的 Go 运行 Travis-CI,用
travis 1 - 检查代码格式是否有错误
diff -u <(echo -n) <(gofmt -d .)
测试
- 测试名称
package_test比package要好 -
go test -short允许减少要运行的测试数
|
|
- 根据系统架构跳过测试
|
|
- 用
testing.AllocsPerRun跟踪你的内存分配 - 多次运行你的基准测试可以避免噪音。
go test -test.bench=. -count=20
工具
-
快速替换
gofmt -w -l -r "panic(err) -> log.Error(err)" . -
go list允许找到所有直接和传递的依赖关系go list -f '{{ .Imports }}' packagego list -f '{{ .Deps }}' package
-
对于快速基准比较,我们有一个
benchstat工具。 -
go-critic linter 从这个文件中强制执行几条建议
-
go mod why -m <module>告诉我们为什么特定的模块在go.mod文件中。 -
GOGC=off go build ...应该会加快构建速度 source -
内存分析器每 512KB 记录一次分配。你能通过
GODEBUG环境变量增加比例,来查看你的文件的更多详细信息。 -
go mod why -m <module>告诉我们为什么特定的模块是在go.mod文件中。
Misc
- dump goroutines https://stackoverflow.com/a/27398062/433041
|
|
-
在编译期检查接口的实现
1var _ io.Reader = (*MyFastReader)(nil) -
len(nil) = 0
-
匿名结构很酷
|
|
-
httputil.DumpRequest是非常有用的东西,不要自己创建 -
获得调用堆栈,我们可以使用
runtime.Caller -
要 marshal 任意的 JSON, 你可以 marshal 为
map[string]interface{}{} -
配置你的
CDPATH以便你能在任何目录执行cd github.com/golang/go- 添加这一行代码到
bashrc(或者其他类似的)export CDPATH=$CDPATH:$GOPATH/src
- 添加这一行代码到
-
从一个 slice 生成简单的随机元素
[]string{"one", "two", "three"}[rand.Intn(3)]
文章作者 Forz
上次更新 2022-04-25 (92d1094)