for循环中return会跳出吗_go开发中10大常见错误

发现一遍写的挺好的文章,自己平时也遇到同类的问题,所以翻译一下并记录下来,原文请看下方链接:

https://itnext.io/the-top-10-most-common-mistakes-ive-seen-in-go-projects-4b79d4f6cd65​itnext.io

Unknown 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/a32a8e9039314a48f03538f3f9535537​gist.github.com

在这个例子中是我们平时开发中常见的类似的数据结构(0.3KB),通过测试,使用值传递比指针快4倍(亲测3倍左右,可能是环境不同),这是为什么呢?

  1. 使用值传递,那么该值就存放在stack中,当函数return时自动释放
  2. 使用指针传递,那么该值会被放在heap中,需要gc
  3. 再次,放在heap中,就需要考虑多个goroutine并发同步问题,需要实时写入heap中

结论:除非是要共享变量,否则都应该传值

Understanding Allocations: the Stack and the Heap - GopherCon SG 2019

https://www.youtube.com/watch?time_continue=3&v=ZMZpH4yT7M0​www.youtube.com

常见heap情况:

  • return 指针变量
  • 接口变量
  • 闭包引用变量
  • size未知或者动态变化的类型:Maps、Channels、Slices、Strings([]byte)

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/errors​github.com
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).
Understanding the context package in golang​p.agnihotry.com
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-1afed51d57fb​medium.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)
  }()
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值