发现一遍写的挺好的文章,自己平时也遇到同类的问题,所以翻译一下并记录下来,原文请看下方链接:
https://itnext.io/the-top-10-most-common-mistakes-ive-seen-in-go-projects-4b79d4f6cd65itnext.ioUnknown Enum Value
type Status uint32
const (
StatusOpen Status = iota // 0
StatusClosed // 1
StatusUnknown // 2
)
type Request struct {
ID int `json:"Id"`
Timestamp int `json:"Timestamp"`
Status Status `json:"Status"`
}
在使用枚举类型时,要注意go默认值(int 默认值为0),当遇到如下json
{
"Id": 1235,
"Timestamp": 1563362390
}
那么默认反序列化到Request Status就是0,对应StatusOpen,这不符合我们的预期
最佳实践应该把unknown设为默认0,如下所示:
type Status uint32
const (
StatusUnknown Status = iota
StatusOpen
StatusClosed
)
Benchmarking
func clear(n uint64, i, j uint8) uint64 {
return (math.MaxUint64<<j | ((1 << i) - 1)) & n
}
func BenchmarkWrong(b *testing.B) {
for i := 0; i < b.N; i++ {
clear(1221892080809121, 10, 63)
}
}
在上述测试用例中,clear是个叶子函数,且没有改变其他值,go编译器默认会优化掉这个函数的调用,然后就达不到测试结果了,改进方法如下:
var result uint64
func BenchmarkCorrect(b *testing.B) {
var r uint64
for i := 0; i < b.N; i++ {
r = clear(1221892080809121, 10, 63)
}
result = r
}
Pass Pointer or Variable
在go中,所有参数传递都是值传递,而且是会copy,对于大的对象传递,通常大家会想到使用指针来优化速度,降低拷贝的消耗,但这样真的会更快吗?
https://gist.github.com/teivah/a32a8e9039314a48f03538f3f9535537gist.github.com在这个例子中是我们平时开发中常见的类似的数据结构(0.3KB),通过测试,使用值传递比指针快4倍(亲测3倍左右,可能是环境不同),这是为什么呢?
- 使用值传递,那么该值就存放在stack中,当函数return时自动释放
- 使用指针传递,那么该值会被放在heap中,需要gc
- 再次,放在heap中,就需要考虑多个goroutine并发同步问题,需要实时写入heap中
结论:除非是要共享变量,否则都应该传值
Understanding Allocations: the Stack and the Heap - GopherCon SG 2019
https://www.youtube.com/watch?time_continue=3&v=ZMZpH4yT7M0www.youtube.com常见heap情况:
- return 指针变量
- 接口变量
- 闭包引用变量
- size未知或者动态变化的类型:Maps、Channels、Slices、Strings([]byte)
![72fb63c071a06257192ee94bb6f04f22.png](https://img-blog.csdnimg.cn/img_convert/72fb63c071a06257192ee94bb6f04f22.png)
# go build -gcflags="-m -l"
Breaking a for/switch or a for/select
对于for循环内部的for/switch/select,break只能跳出一层
for {
switch f() {
case true:
break
case false:
// Do something
}
}
for {
select {
case <-ch:
// Do something
case <-ctx.Done():
break
}
}
通过使用break label跳出到指定层
loop:
for {
select {
case <-ch:
// Do something
case <-ctx.Done():
break loop
}
}
Errors Management
An error should be handled only once. Logging an error is handling an error. So an error should either be logged or propagated.
错误日志只应该被处理一次,要么logging,要么传播
如果需要将error传播到上层,建议使用pkg/errors,可以实现层次errors结构
pkg/errorsgithub.com![4db794edc604f6584cb0c11af1f773bb.png](https://img-blog.csdnimg.cn/img_convert/4db794edc604f6584cb0c11af1f773bb.png)
unable to server HTTP POST request for customer 1234
|_ unable to insert customer contract abcd
|_ unable to commit transaction
Slice Initialization
前提:知道slice长度,且不需要增长
直接用index坐标赋值更快
用append代码风格更一致
Context Management
A context can carry:
- A deadline. It means either a duration (e.g. 250 ms) or a date-time (e.g.
2019-01-08 01:00:00
) by which we consider that if it is reached, we must cancel an ongoing activity (an I/O request, awaiting a channel input, etc.). - A cancelation signal (basically a
<-chan struct{}
). Here, the behavior is similar. Once we receive a signal, we must stop an ongoing activity. For example, let’s imagine that we receive two requests. One to insert some data and another one to cancel the first request (because it’s not relevant anymore or whatever). This could be achieved by using a cancelable context in the first call that would be then canceled once we get the second request. - A list of key/value (both based on an
interface{}
type).
![b7effb5fa29a9e95182fb6a947d404be.png](https://img-blog.csdnimg.cn/img_convert/b7effb5fa29a9e95182fb6a947d404be.png)
Using the -race Option
the Go race detector will not help for every single concurrency problems. Nevertheless, it isvaluabletooling and we should always enable it while testing our applications.
https://medium.com/@val_deleplace/does-the-race-detector-catch-all-data-races-1afed51d57fbmedium.com- The race detector is easy to use. I strongly recommend it.
- It can’t tell you all of the bugs lurking in your code.
- But it shouts a WARNING for every data race that occurs.
- Almost all of the races: very few false negatives!
- Only true races: no false positives!
Don't Using a Filename as an Input
尽量使用io.Reader/Writer bufio.Reader/Writer更通用
使用filename限定在file上,但使用io接口可以适配file、http、stdxxx等等
Goroutines and Loop Variables
ints := []int{1, 2, 3}
for _, i := range ints {
go func() {
fmt.Printf("%vn", i)
}()
}
期望输出:1 n 2 n 3 n
实际输出:3 n 3 n 3 n
这里其实是个标准的闭包用法,不仅是go,其他语言也一样,闭包是引用变量,在实际运行时引用的变量可能已经变了
闭包常用于go的sync.WaitGroup,所有goroutine使用同一个wg
避免闭包的用法:
// 使用参数调用函数
ints := []int{1, 2, 3}
for _, i := range ints {
go func(i int) {
fmt.Printf("%vn", i)
}(i)
}
// 创建新的变量
ints := []int{1, 2, 3}
for _, i := range ints {
i := i
go func() {
fmt.Printf("%vn", i)
}()
}