Golang可能会踩的58个坑之初级篇

前言Go 是一门简单有趣的编程语言,与其他语言一样 , 在使用时不免会遇到很多坑,不过它们大多不是 Go 本身的设计缺陷 。如果你刚从其他语言转到 Go , 那这篇文章里的坑多半会踩到 。
如果花时间学习官方 doc、wiki、讨论邮件列表、 Rob Pike 的大量文章以及 Go 的源码,会发现这篇文章中的坑是很常见的 , 跳过这些坑,能减少大量调试代码的时间 。
1.1.1. 初级篇:1-351.左大括号 { 不能单独放一行在其他大多数语言中,{ 的位置你自行决定 。Go比较特别,遵守分号注入规则(automatic semicolon injection):编译器会在每行代码尾部特定分隔符后加;来分隔多条语句,比如会在 ) 后加分号:
// 错误示例func main(){println("www.baidu.com")}// 等效于func main();// 无函数体{println("hello world")}./main.go: missing function body./main.go: syntax error: unexpected semicolon or newline before {// 正确示例func main() {println("www.baidu.com")}2.未使用的变量如果在函数体代码中有未使用的变量 , 则无法通过编译 , 不过全局变量声明但不使用是可以的 。即使变量声明后为变量赋值 , 依旧无法通过编译,需在某处使用它:
// 错误示例var gvar int// 全局变量 , 声明不使用也可以func main() {var one int// error: one declared and not usedtwo := 2// error: two declared and not usedvar three int// error: three declared and not usedthree = 3}// 正确示例// 可以直接注释或移除未使用的变量func main() {var one int_ = onetwo := 2println(two)var three intone = threevar four intfour = four}3.未使用的 import如果你 import一个包,但包中的变量、函数、接口和结构体一个都没有用到的话,将编译失败 。可以使用 _下划线符号作为别名来忽略导入的包,从而避免编译错误,这只会执行 package 的 init()
// 错误示例import ("fmt"// imported and not used: "fmt""log"// imported and not used: "log""time"// imported and not used: "time")func main() {}// 正确示例// 可以使用 goimports 工具来注释或移除未使用到的包import (_ "fmt""log""time")func main() {_ = log.Println_ = time.Now}4.简短声明的变量只能在函数内部使用// 错误示例myvar := 1// syntax error: non-declaration statement outside function bodyfunc main() {}// 正确示例varmyvar = 1func main() {}5.使用简短声明来重复声明变量不能用简短声明方式来单独为一个变量重复声明,:=左侧至少有一个新变量,才允许多变量的重复声明:
// 错误示例func main() {one := 0one := 1 // error: no new variables on left side of :=}// 正确示例func main() {one := 0one, two := 1, 2// two 是新变量 , 允许 one 的重复声明 。比如 error 处理经常用同名变量 errone, two = two, one// 交换两个变量值的简写}6.不能使用简短声明来设置字段的值struct 的变量字段不能使用 := 来赋值以使用预定义的变量来避免解决:
// 错误示例type info struct {result int}func work() (int, error) {return 3, nil}func main() {var data infodata.result, err := work()// error: non-name data.result on left side of :=fmt.Printf("info: %+v\n", data)}// 正确示例func main() {var data infovar err error// err 需要预声明data.result, err = work()if err != nil {fmt.Println(err)return}fmt.Printf("info: %+v\n", data)}7.不小心覆盖了变量对从动态语言转过来的开发者来说,简短声明很好用,这可能会让人误会 := 是一个赋值操作符 。如果你在新的代码块中像下边这样误用了 := , 编译不会报错,但是变量不会按你的预期工作:
func main() {x := 1println(x)// 1{println(x)// 1x := 2println(x)// 2// 新的 x 变量的作用域只在代码块内部}println(x)// 1}这是 Go 开发者常犯的错,而且不易被发现 。可使用 vet工具来诊断这种变量覆盖,Go 默认不做覆盖检查,添加 -shadow 选项来启用:
> go tool vet -shadow main.gomain.go:9: declaration of "x" shadows declaration at main.go:5注意 vet 不会报告全部被覆盖的变量 , 可以使用 go-nyet 来做进一步的检测:
> $GOPATH/bin/go-nyet main.gomain.go:10:3:Shadowing variable `x`8.显式类型的变量无法使用 nil 来初始化nil 是 interface、function、pointer、map、slice 和 channel 类型变量的默认初始值 。但声明时不指定类型 , 编译器也无法推断出变量的具体类型 。
// 错误示例func main() {var x = nil// error: use of untyped nil_ = x}// 正确示例func main() {var x interface{} = nil_ = x}9.直接使用值为 nil 的 slice、map允许对值为 nil 的 slice 添加元素 , 但对值为 nil 的 map添加元素则会造成运行时 panic

推荐阅读