Why your golang sucks:每个人都会踩的GO的五十个坑 (1-10)
1.本文是我在翻GO文章时发现的,译者主要完成了1-22条的翻译(有不少问题。),没了后续,本系列文章将会先转载并修订他最初的翻译,然后完成第23-50条的翻译
2.由于本篇文章最初写自2015年,而GO也在这两年间发生了较大变化,以下的50个坑将会重新实验修正,将会与原文稍微有些区别
3.本文翻译自 50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs
4.http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html
Go语言是一个简单却蕴含深意的语言。但是,即便号称是最简单的C语言,都能总结出一本《C陷阱与缺陷》,更何况Go语言呢。Go语言中的许多坑其实并不是因为Go自身的问题。一些错误你再别的语言中也会犯,例如作用域,一些错误就是对因为 Go 语言的特性不了解而导致的,例如 range。
其实如果你在学习Go语言的时候去认真地阅读官方文档,百科,邮件列表或者其他的类似 Rob Pike 的名人博客,报告,那么本文中提到的许多坑都可以避免。但是不是每个人都会从基础学起,例如译者就喜欢简单粗暴地直接用Go语言写程序。如果你也像译者一样,那么你应该读一下这篇文章:这样可以避免在调试程序时浪费过多时间。
本文将50个坑按照使用使用范围和难易程度分为以下三个级别:“新手入门级”,“新手深入级”,“新手进阶级”。
1.“{”不能单独放在一行
级别:新手入门级
Go语言设计者肯定和C语言设计者(K&R)有种不明不白的关系,因为C语言中的K&R格式在Go语言中得到发扬光大。大多数语言中,大括号中的左括号是可以随便放在哪里的:C语言中必须要按照K&R格式对代码进行格式化之后,左括号才会被放在前一行中的最后。但是Go语言中,左括号必须强制不能单独放在一行。这个规则得益于“自动分号注射”(automatic semicolon injection)。
补充:go提供了专门用于格式化代码的gofmt工具。
出错代码:
package main
import "fmt"
func main()
{ //{不能单独一行
fmt.Println("hello there!")
}
错误信息:
./main.go:6: syntax error: unexpected semicolon or newline before {
修正代码:
package main
import "fmt"
func main() {
fmt.Println("works!")
}
2.未使用已定义的变量
级别:新手入门级
如果代码中有未使用的变量,那个代码编译的时候就会报错。Go要求在代码中所有声明的变量都需要被用到,当然,全局变量除外。
函数的参数也可以只被声明,不被使用。
对于未声明变量的调用同样会导致编译失败。
出错代码:
package main
var gvar int //not an error
func main() {
var one int //error, unused variable
two := 2 //error, unused variable
var three int //error, even though it's assigned 3 on the next line
three = 3
}
错误信息:
./main.go:7: one declared and not used
./main.go:8: two declared and not used
./main.go:9: three declared and not used
修正代码:
错误的修正方式(手工赋值,规避掉错误)
package main
func main() {
var one int
_ = one
two := 2
fmt.Println(two)
var three int
three = 3
one = three
var four int
four = four
}
不用的变量就要删掉
package main
import "fmt"
var gvar int //not an error
func main() {
three := 3
fmt.Println("three:", three)
}
3.未使用的包
级别:新手入门级
当import一个包之后,如果不使用这个包,或者这个包中的函数/接口/数据结构/变量,那么将会编译失败。
可以删掉该import引用,或者再包前面加上”_”符号
出错代码:
package main
import (
"fmt"
"log"
"time"
)
func main() {
}
错误信息:
# command-line-arguments
./50demo.go:4: imported and not used: "fmt"
./50demo.go:5: imported and not used: "log"
./50demo.go:6: imported and not used: "time"
修正代码
package main
import (
_ "fmt"
"log"
"time"
)
var _ = log.Println
func main() {
_ = time.Now
}
对于”“符号的理解不要将其仅仅理解为解决未使用包的编译错误,import 引用该包 该包下的文件里所有init()函数都会被引用,其他的函数等并不会被引用进来,且无法通过包名来调用包中的其他函数。比如一个很常见的用法,
import _ "github.com/go-sql-driver/mysql"
4.只能在函数内部使用精简声明赋值
级别:新手入门级
出错代码:
package main
myvar := 1 //error, 精简声明赋值
func main() {
}
错误信息:
# command-line-arguments
./50demo.go:3: syntax error: non-declaration statement outside function body
修正代码:
package main
var myvar = 1
func main() {
}
5.无法使用精简声明赋值语句对变量重新赋值
级别:新手入门级
不能使用精简的声明赋值语句重新赋值变量,但是可以使用精简的赋值语句同时声明赋值多个变量。
当然,声明的变量要在其作用域内使用,否则有变量未使用错误,或者可以用”_”符号来匿名该变量
出错代码:
package main
func main() {
one := 0
one := 1 //error
}
错误信息:
# command-line-arguments
./50demo.go:5: no new variables on left side of :=
修正代码:
package main
func main() {
one := 0
one, two := 1,2
one,two = two,one
_, three := 0, 3
one = three
}
6.不能对结构体中的参数使用精简声明赋值
级别:新手入门级
出错代码:
package main
import (
"fmt"
)
type info struct {
result int
}
func work() (int,error) {
return 13,nil
}
func main() {
var data info
data.result, err := work() //error
fmt.Printf("info: %+v\n",data)
}
错误信息:
# command-line-arguments
./50demo.go:18: non-name data.result on left side of :=
直白的来讲,无法声明data.result这样一个变量
修正代码:
package main
import (
"fmt"
)
type info struct {
result int
}
func work() (int,error) {
return 13,nil
}
func main() {
var data info
var err error
data.result, err = work() //ok
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("info: %+v\n",data) //prints: info: {result:13}
}
7.注意局部变量作用域
级别:新手入门级
和 C 语言一样,Go 语言也有作用于,一个变量的作用范围仅仅是一个代码块。虽然精简的赋值语句很简单,但是注意作用域。
package main
import (
"fmt"
)
func main() {
var x int = 1
fmt.Println(x) //打印 1
a, x := 0, 3
fmt.Println(x) //打印 3
fmt.Println(a) //打印 0
{
fmt.Println(x) //打印 3
a, x := 10, 30
fmt.Println(x) //打印 30
fmt.Println(a) //打印 10
}
fmt.Println(x) //打印 3
}
在作者的这个例子之外,举一个go常用的回滚场景
8.无法用 nil 对不使用显式类型的变量赋值
级别:新手入门级
nil 可以用作 interface、function、pointer、map、slice 和 channel 的“空值”。但是如果不特别指定的话,Go 语言不能识别类型,所以会报错。
错误信息:
package main
func main() {
var x = nil //error
_ = x
}
错误信息:
# command-line-arguments
./50demo.go:28: use of untyped nil
修正代码:
package main
func main() {
var x interface{} = nil
_ = x
}
9.Slice 和 Map 的 nil 值
级别:新手入门级
初始值为 nil 的 Slice 是可以进行“添加”操作的,但是对于 Map 的“添加”操作会导致运行时panic。
正确代码:
package main
func main() {
var s []int //nil
s = append(s,1) //ok
}
错误代码:
package main
func main() {
var m map[string]int //nil
m["one"] = 1 //panic, need alloc(make) at first
}
10.Map和cap()
级别:新手入门级
cap()函数返回的是数组切片分配的空间大小
创建 Map 的时候可以指定 Map 的长度,但是在运行时是无法使用 cap() 获取其容量
错误信息:
package main
func main() {
m := make(map[string]int,99)
cap(m) //error
}
错误信息:
# command-line-arguments
./50demo.go:29: invalid argument m (type map[string]int) for cap