Golang 是一门简单高效的编程语言,学习曲线平缓,开发效率高。不过,和使用其它语言一样,在编码的过程中也难免会踩到一些坑,这些坑一般都是开发者不熟悉 Golang 的一些特性导致的。本篇文章总结了一些常见的坑,希望大家在遇到类似情况的时候能够绕过这些坑,提高开发和联调效率。
1. Golang 中的对象
type Student struct {
}
func (s *Student) printName() {
fmt.Println("Tom") // Tom
}
func main() {
var s *Student
fmt.Println("s == nil?", s == nil) // s == nil? true
s.printName()
}
上述代码是可以正常输出的,这在 Java 等面向对象语言中是不可思议的。Golang 不是真正意义上的面向对象语言,Golang 中的对象其实是 struct 实体。
2. 简短声明的变量不能用在函数外部
// 错误示例
a := 1 // syntax error: non-declaration statement outside function body
func foo() {
fmt.Println(a)
}
// 正确示例
var a = 1
func foo() {
fmt.Println(a)
}
3. 简短声明不能用于设置结构体的字段
// 错误示例
type response struct {
code int
}
func foo() (int, error) {
return 3, nil
}
func main() {
var resp response
resp.code, err := foo() // non-name resp.code on left side of :=
if err != nil {
fmt.Println(err)
}
}
// 正确示例
type response struct {
code int
}
func foo() (int, error) {
return 3, nil
}
func main() {
var resp response
var err error
resp.code, err = foo()
if err != nil {
fmt.Println(err)
}
}
4. for range 迭代 slice, array 时更新元素
在 for range 迭代中,遍历的值是元素的值拷贝,更新拷贝并不会更新原始的元素。
type Student struct {
name string
score int
}
func main() {
students := []Student{{"Tom", 58}, {"Lucy", 59}}
for _, s := range students {
s.score = 60
}
fmt.Println("students:", students) // students: [{Tom 58} {Lucy 59}]
}
上述代码,变量 s 是值拷贝,所以导致更新 s 的 score 字段并没有对原值的 score 字段生效。正确更改原始元素的 score 字段,需要用索引访问到原始元素进行更改。
type Student struct {
name string
score int
}
func main() {
students := []Student{{"Tom", 58}, {"Lucy", 59}}
for index := range students {
students[index].score = 60
}
fmt.Println("students:", students) // students: [{Tom 60} {Lucy 60}]
}
5. 在为 nil 的 channel 上发送和接收数据将会永远阻塞
func main() {
var ch chan int
fmt.Println("ch == nil?", ch == nil) // ch == nil? true
go func() {
ch <- 1
}()
fmt.Println("element of ch: ", <-ch)
}
上述代码会报如下的死锁错误,而不是 NPE 异常:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive (nil chan)]
goroutine 5 [chan send (nil chan)]
6. defer 函数的传参
对于 defer 延迟执行的函数,传参在声明的时候就会求出具体值,而不是在执行时才求值。
func foo(x int) {
fmt.Println("x in foo:", x) // x in foo: 1
}
func main() {
x := 1
defer foo(x)
x += 1
fmt.Println("x in main:", x) // x in main: 2
}
7. interface 变量和 nil 比较
很多人把 interface 误以为是指针类型,但是其实它不是。如果将 interface 变量和 nil 进行比较,只有类型和值都为 nil 时,两者才相等。
func main() {
var x *int
var i interface{}
fmt.Printf("%v %v\n", x, x == nil) // <nil> true
fmt.Printf("%T %v %v\n", i, i, i == nil) // <nil> <nil> true
i = x
fmt.Printf("%T %v %v\n", i, i, i == nil) // *int <nil> false
}
如果函数的返回值是 interface 类型,则要格外小心这个坑。
type response struct {
code int
}
func foo(x int) interface{} {
var resp *response = nil
if x < 0 {
return resp
}
return &response{0}
}
func main() {
if resp := foo(-1); resp == nil {
fmt.Println("invalid parameter, resp:", resp)
} else {
fmt.Println("work fine, resp:", resp) // work fine, resp: <nil>
}
}
上述代码,main 函数进入了 else 逻辑打印出了 "work fine, resp: <nil>" ,原因就是在 foo 函数中 if 逻辑返回的 resp 变量虽然值是 nil,但是类型并非 nil。如果想改正上述代码,则需要在 if 逻辑中明确返回 nil。
type response struct {
code int
}
func foo(x int) interface{} {
if x < 0 {
return nil
}
return &response{0}
}
func main() {
if resp := foo(-1); resp == nil {
fmt.Println("invalid parameter, resp:", resp) // invalid parameter, resp: <nil>
} else {
fmt.Println("work fine, resp:", resp)
}
}
8. 结构体的私有字段无法被序列化
结构体小写字母开头的私有字段无法被外部直接访问到,因此在进行 json, xml 等格式的序列化操作时,这些字段将会被忽略,相应地在反序列化时得到的是零值。
type Student struct {
Name string
score int // 私有字段
}
func main() {
s1 := Student{"Tom", 90}
buf, _ := json.Marshal(s1)
fmt.Println(string(buf)) // {"Name":"Tom"}
var s2 Student
json.Unmarshal(buf, &s2)
fmt.Printf("%+v\n", s2) // {Name:Tom score:0}
}