微信号:go-programming-lang

介绍:重点介绍go语言的高级功能,比如interface、 reflection、goroutine、concurrency、memory model、testing、functional、toolchains等.

Google程序员踩过哪些Go语言的坑?

2016-07-29 07:07 Oscar 译

简介

在Go语言中,有三个地方很容易犯错误。在很多项目中,这些错误以一种极其隐蔽、不易理解的方式存在着。


本文中提到的三个错误曾经存在于Kubernetes项目的代码中,并且成功避开过不止一次code review


注:Kubernetes是Google主导的容器编排工具,github上star数超过15k。


阅读建议

前方代码较多,为避免换行,建议手机横屏阅读。阅读时,尽量思考清楚,然后再看下面的答案。


如果对某些细节不太明白,建议在电脑上运行一下。本地没有编译器的话,推荐 play.golang.org。


本文涉及到的知识点包括:

  1. 块作用域 (尤其是 for-loop 作用域)

  2. defer "后进先出"的特性

  3. go 启动一个goroutine的某些细节 (参考上一篇“go语言内存模型”)

  4. 匿名函数(闭包)

  5. go语言类型的底层表示,尤其是interface(reflection基础)

  6. 变量遮罩(shadowing)

一、循环中的变量在循环之外被使用。

阅读以下下面的代码,思考一下执行结果,然后再翻到下面看答案。

func print(pi *int) { fmt.Println(*pi) }

for i := 0; i < 10; i++ {
 defer fmt.Println(i)
 defer func(){ fmt.Println(i) }()
 defer func(i int){ fmt.Println(i) }(i)
 defer print(&i)
  go fmt.Println(i)
  go func(){ fmt.Println(i) }()
}

答案:

func print(pi *int) { fmt.Println(*pi) }

for i := 0; i < 10; i++ {
 // OK; 打印 9 ... 0
  defer fmt.Println(i)

  // WRONG; 打印10次10
  defer func(){ fmt.Println(i) }()
  // OK; 打印 9 ... 0
  defer func(i int){ fmt.Println(i) }(i)

  // WRONG; 打印10次10
  defer print(&i)
  // OK; 打印0 ... 9,但是顺序无法预测
  go fmt.Println(i)

  // WRONG; 完全不可预测
  go func(){ fmt.Println(i) }()
}

for key, value := range myMap {
 // 与上面的slice循环结果一致
}

我们都希望这些值的作用域是在循环内部,但是在每次迭代中,Go重用了相同的内存地址。


这意味着你永远不要让 key,value 或 i 溢出单个循环。匿名函数 (闭包)

func() { /* do something with i */ } 

会以一种微妙的方式使用变量地址。

二、Nil interface不等价于包含一个nil指针的interface。

type Cat interface {
 Meow()
}

type Tabby struct {}
func (*Tabby) Meow() { fmt.Println("meow") }

func GetACat() Cat {
 var myTabby *Tabby = nil
 // 天哦,忘记给 myTabby 赋值了
 return myTabby
}

func TestGetACat(t *testing.T) {
 if GetACat() == nil {
   t.Errorf("忘记返回一个真实的 Cat!")
 }
}


猜一下会发生什么。上面的测试函数根本不会报错!


因为 interface 作为一个指针存在,所以 GetACat 会返回一个指向 nil 指针的指针。


千万不要像 GetACat 这样使用 nil ,相信我,你和同事的幸福感会更高。


在使用 error 值时,很容易犯这个错误。像了解更多的话,读下golang 的faq:http://golang.org/doc/faq#nil_error

三、变量遮罩有伤心智。

var ErrDidNotWork = errors.New("did not work")

func DoTheThing(reallyDoIt bool) (err error) {
 if reallyDoIt {
   result, err := tryTheThing()
   if err != nil || result != "it worked" {
     err = ErrDidNotWork
   }
 }
 return err
}

上面的函数总是返回 nil,因为 if 语句块内的 err 变量会遮罩函数作用域内的 err 变量。 


下期预告:Go语言反射三定律(https://blog.golang.org/laws-of-reflection)


原文链接:https://gist.github.com/lavalamp/4bd23295a9f32706a48f


 
深入Go语言 更多文章 Go语言并发模型:使用 context Go语言并发模型:以并行处理MD5为例 Go语言并发模型:像Unix Pipe那样使用channel Go语言反射三定律
猜您喜欢 Linux内核之旅—微信平台开篇 我的 iOS 开发入门自学路径 揭秘海外华为人的真实生活 传智播客Java名师答疑:面试题中常见的String类问题 SegmentFault专访Face++ —— 世界领先的人脸识别云服务平台