Go語言是一個簡單卻蘊含深意的語言。但是,即便號稱是最簡單的C語言,都能總結出一本《C陷阱與缺陷》,更何況Go語言呢。Go語言中的許多坑其實並不是因為Go自身的問題。一些錯誤你再別的語言中也會犯,例如作用域,一些錯誤就是對因為 Go 語言的特性不了解而導致的,例如 range。
其實如果你在學習Go語言的時候去認真地閱讀官方文檔,百科,郵件列表或者其他的類似 Rob Pike 的名人博客,報告,那么本文中提到的許多坑都可以避免。但是不是每個人都會從基礎學起,例如譯者就喜歡簡單粗暴地直接用Go語言寫程序。如果你也像譯者一樣,那么你應該讀一下這篇文章:這樣可以避免在調試程序時浪費過多時間。
本文將50個坑按照使用使用范圍和難易程度分為以下三個級別:“新手入門級”,“新手深入級”,“新手進階級”。
“{”不能單獨放在一行
級別:新手入門級
Go語言設計者肯定和C語言設計者(K&R)有種不明不白的關系,因為C語言中的K&R格式在Go語言中得到發揚光大。大多數語言中,大括號中的左括號是可以隨便放在哪里的:C語言中必須要按照K&R格式對代碼進行格式化之后,左括號才會被放在前一行中的最后。但是Go語言中,左括號必須強制不能單獨放在一行。這個規則得益於“自動分號注射”(automatic semicolon injection)。
補充:go提供了專門用於格式化代碼的gofmt工具。
出錯代碼:
package main import "fmt" func main() { //error, can't have the opening brace on a separate line fmt.Println("hello there!") }
錯誤信息:
/tmp/sandbox826898458/main.go:6: syntax error: unexpected semicolon or newline before {
修正代碼:
package main import "fmt" func main() { fmt.Println("works!") }
未使用已定義的變量
級別:新手入門級
如果代碼中有未使用的變量,那個代碼編譯的時候就會報錯。Go要求在代碼中所有聲明的變量都需要被用到,當然,全局變量除外。函數的參數也可以只被聲明,不被使用。
對於未聲明變量的調用同樣會導致編譯失敗。和C語言一樣,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 func(unused string) { fmt.Println("Unused arg. No compile error") }("what?") }
錯誤信息:
/tmp/sandbox473116179/main.go:6: one declared and not used /tmp/sandbox473116179/main.go:7: two declared and not used /tmp/sandbox473116179/main.go:8: three declared and not used
修正代碼:
package main
import "fmt" func main() { var one int _ = one two := 2 fmt.Println(two) var three int three = 3 one = three var four int four = four }
當然,你也可以考慮刪除那些沒有使用的變量。
未使用的包
級別:新手入門級
當import一個包之后,如果不使用這個包,或者這個包中的函數/接口/數據結構/變量,那么將會編譯失敗。
如果真的確認要引入變量但是不使用的話,我們可以用“”標識符坐標記,避免編譯失敗。“”標識符表示為了得到這些包的副作用而引入這些包。
出錯代碼:
package main import ( "fmt" "log" "time" ) func main() { }
錯誤信息:
/tmp/sandbox627475386/main.go:4: imported and not used: "fmt" /tmp/sandbox627475386/main.go:5: imported and not used: "log" /tmp/sandbox627475386/main.go:6: imported and not used: "time"
修正代碼
package main import ( _ "fmt" "log" "time" ) var _ = log.Println func main() { _ = time.Now }
只能在函數內部使用簡短的變量聲明
級別:新手入門級 出錯代碼:
package main myvar := 1 //error func main() { }
錯誤信息:
/tmp/sandbox265716165/main.go:3: non-declaration statement outside function body
修正代碼:
package main
var myvar = 1 func main() { }
無法使用精簡的賦值語句對變量重新賦值
級別:新手入門級
不能使用精簡的賦值語句重新賦值單個變量,但是可以使用精簡的賦值語句同時賦值多個變量。
並且,重定義的變量必須寫在同一個代碼塊。
錯誤信息:
package main func main() { one := 0 one := 1 //error }
錯誤信息:
/tmp/sandbox706333626/main.go:5: no new variables on left side of :=
修正代碼:
package main func main() { one := 0 one, two := 1,2 one,two = two,one }
隱式變量(作用域)
級別:新手入門級
和 C 語言一樣,Go 語言也有作用於,一個變量的作用范圍僅僅是一個代碼塊。雖然精簡的賦值語句很簡單,但是注意作用域。
package main import "fmt" func main() { x := 1 fmt.Println(x) //打印 1 { fmt.Println(x) //打印 1 x := 2 fmt.Println(x) //打印 2 } fmt.Println(x) //打印 1 ( 不是 2) }
甚至對於有經驗的開發者來說,這也是個不注意就會掉進去的深坑。
除非特別指定,否則無法使用 nil 對變量賦值
級別:新手入門級
nil 可以用作 interface、function、pointer、map、slice 和 channel 的“空值”。但是如果不特別指定的話,Go 語言不能識別類型,所以會報錯。
錯誤信息:
package main func main() { var x = nil //error _ = x }
錯誤信息:
/tmp/sandbox188239583/main.go:4: use of untyped nil
修正代碼:
package main func main() { var x interface{} = nil _ = x }
Slice 和 Map 的 nil 值
級別:新手入門級
初始值為 nil 的 Slice 是可以進行“添加”操作的,但是對於 Map 的“添加”操作會導致運行時恐慌。( ﹁ ﹁ ) 恐慌。
修正代碼:
package main func main() { var s []int s = append(s,1) }
錯誤信息:
package main func main() { var m map[string]int m["one"] = 1 //error }
Map 定長
級別:新手入門級
創建 Map 的時候可以指定 Map 的長度,但是在運行時是無法使用 cap() 功能重新指定 Map 的大小,Map 是定長的。
錯誤信息:
package main func main() { m := make(map[string]int,99) cap(m) //error }
錯誤信息:
/tmp/sandbox326543983/main.go:5: invalid argument m (type map[string]int) for cap
字符串無法為 nil
級別:新手入門級
所有的開發者都可能踩的坑,在 C 語言中是可以 char *String=NULL,但是 Go 語言中就無法賦值為 nil。
錯誤信息:
package main func main() { var x string = nil //error if x == nil { //error x = "default" } }
Compile Errors:
/tmp/sandbox630560459/main.go:4: cannot use nil as type string in assignment /tmp/sandbox630560459/main.go:6: invalid operation: x == nil (mismatched types string and nil)
修正代碼:
package main func main() { var x string //defaults to "" (zero value) if x == "" { x = "default" } }
參數中的數組
Array Function Arguments
級別:新手入門級 對於 C 和 C++ 開發者來說,數組就是指針。給函數傳遞數組就是傳遞內存地址,對數組的修改就是對原地址數據的修改。但是 Go 語言中,傳遞的數組不是內存地址,而是原數組的拷貝,所以是無法通過傳遞數組的方法去修改原地址的數據的。
package main import "fmt" func main() { x := [3]int{1,2,3} func(arr [3]int) { arr[0] = 7 fmt.Println(arr) //prints [7 2 3] }(x) fmt.Println(x) //prints [1 2 3] (not ok if you need [7 2 3]) }
如果需要修改原數組的數據,需要使用數組指針(array pointer)。
package main import "fmt" func main() { x := [3]int{1,2,3} func(arr *[3]int) { (*arr)[0] = 7 fmt.Println(arr) //prints &[7 2 3] }(&x) fmt.Println(x) //prints [7 2 3] }
或者可以使用 Slice,
package main import "fmt" func main() { x := []int{1,2,3} func(arr []int) { arr[0] = 7 fmt.Println(arr) //prints [7 2 3] }(x) fmt.Println(x) //prints [7 2 3] }
使用 Slice 和 Array 的 range 會導致預料外的結果
級別:新手入門級
如果你對別的語言中的 for in 和 foreach 熟悉的話,那么 Go 中的 range 使用方法完全不一樣。因為每次的 range 都會返回兩個值,第一個值是在 Slice 和 Array 中的編號,第二個是對應的數據。
出錯代碼:
package main import "fmt" func main() { x := []string{"a","b","c"} for v := range x { fmt.Println(v) //prints 0, 1, 2 } }
修正代碼:
package main import "fmt" func main() { x := []string{"a","b","c"} for _, v := range x { fmt.Println(v) //prints a, b, c } }
