[轉載][翻譯]Go的50坑:新Golang開發者要注意的陷阱、技巧和常見錯誤[1]
Go是一門簡單有趣的語言,但與其他語言類似,它會有一些技巧。。。這些技巧的絕大部分並不是Go的缺陷造成的。如果你以前使用的是其他語言,那麼這其中的有些錯誤就是很自然的陷阱。其它的是由錯誤的假設和缺少細節造成的。
如果你花時間學習這門語言,閱讀官方說明、wiki、郵件列表討論、大量的優秀博文和Rob Pike的展示,以及原始碼,這些技巧中的絕大多數都是顯而易見的。儘管不是每個人都是以這種方式開始學習的,但也沒關係。如果你是Go語言新人,那麼這裡的資訊將會節約你大量的除錯程式碼的時間。
目錄
- 初級篇
- 開大括號不能放在單獨的一行
- 未使用的變數
- 未使用的Imports
- 簡式的變數宣告僅可以在函式內部使用
- 使用簡式宣告重複宣告變數
- 偶然的變數隱藏Accidental Variable Shadowing
- 不使用顯式型別,無法使用“nil”來初始化變數
- 使用“nil” Slices and Maps
- Map的容量
- 字串不會為“nil”
- Array函式的引數
- 在Slice和Array使用“range”語句時的出現的不希望得到的值
- Slices和Arrays是一維的
- 訪問不存在的Map Keys
- Strings無法修改
- String和Byte Slice之間的轉換
- String和索引操作
- 字串不總是UTF8文字
- 字串的長度
- 在多行的Slice、Array和Map語句中遺漏逗號
- log.Fatal和log.Panic不僅僅是Log
- 內建的資料結構操作不是同步的
- String在“range”語句中的迭代值
- 對Map使用“for range”語句迭代
- "switch"宣告中的失效行為
- 自增和自減
- 按位NOT操作
- 操作優先順序的差異
- 未匯出的結構體不會被編碼
- 有活動的Goroutines下的應用退出
- 向無快取的Channel傳送訊息,只要目標接收者準備好就會立即返回
- 向已關閉的Channel傳送會引起Panic
- 使用"nil" Channels
- 傳值方法的接收者無法修改原有的值
- 進階篇
- 關閉HTTP的響應
- 關閉HTTP的連線
- 比較Structs, Arrays, Slices, and Maps
- 從Panic中恢復
- 在Slice, Array, and Map "range"語句中更新引用元素的值
- 在Slice中"隱藏"資料
- Slice的資料“毀壞”
- "走味的"Slices
- 型別宣告和方法
- 從"for switch"和"for select"程式碼塊中跳出
- "for"宣告中的迭代變數和閉包
- Defer函式呼叫引數的求值
- 被Defer的函式呼叫執行
- 失敗的型別斷言
- 阻塞的Goroutine和資源洩露
- 高階篇
- 使用指標接收方法的值的例項
- 更新Map的值
- "nil" Interfaces和"nil" Interfaces的值
- 棧和堆變數
- GOMAXPROCS, 併發, 和並行
- 讀寫操作的重排順序
- 優先排程
初級篇
開大括號不能放在單獨的一行
- level: beginner
在大多數其他使用大括號的語言中,你需要選擇放置它們的位置。Go的方式不同。你可以為此感謝下自動分號的注入(沒有預讀)。是的,Go中也是有分號的:-)
失敗的例子:
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!")
}
未使用的變數
- level: beginner
如果你有未使用的變數,程式碼將編譯失敗。當然也有例外。在函式內一定要使用宣告的變數,但未使用的全域性變數是沒問題的。
如果你給未使用的變數分配了一個新的值,程式碼還是會編譯失敗。你需要在某個地方使用這個變數,才能讓編譯器愉快的編譯。
Fails:
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
}
Compile Errors:
/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
Works:
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
}
另一個選擇是註釋掉或者移除未使用的變數 :-)
未使用的Imports
- level: beginner
如果你引入一個包,而沒有使用其中的任何函式、介面、結構體或者變數的話,程式碼將會編譯失敗。
如果你真的需要引入的包,你可以新增一個下劃線標記符, _
,來作為這個包的名字,從而避免編譯失敗。下滑線標記符用於引入,但不使用。
Fails:
package main
import (
"fmt"
"log"
"time"
)
func main() {
}
Compile Errors:
/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"
Works:
package main
import (
_ "fmt"
"log"
"time"
)
var _ = log.Println
func main() {
_ = time.Now
}
另一個選擇是移除或者註釋掉未使用的imports :-)
簡式的變數宣告僅可以在函式內部使用
- level: beginner
Fails:
package main
myvar := 1 //error
func main() {
}
Compile Error:
/tmp/sandbox265716165/main.go:3: non-declaration statement outside function body
Works:
package main
var myvar = 1
func main() {
}
使用簡式宣告重複宣告變數
- level: beginner
你不能在一個單獨的宣告中重複宣告一個變數,但在多變數宣告中這是允許的,其中至少要有一個新的宣告變數。
重複變數需要在相同的程式碼塊內,否則你將得到一個隱藏變數。
Fails:
package main
func main() {
one := 0
one := 1 //error
}
Compile Error:
/tmp/sandbox706333626/main.go:5: no new variables on left side of :=
Works:
package main
func main() {
one := 0
one, two := 1,2
one,two = two,one
}
偶然的變數隱藏Accidental Variable Shadowing
- level: beginner
短式變數宣告的語法如此的方便(尤其對於那些使用過動態語言的開發者而言),很容易讓人把它當成一個正常的分配操作。如果你在一個新的程式碼塊中犯了這個錯誤,將不會出現編譯錯誤,但你的應用將不會做你所期望的事情。
package main
import "fmt"
func main() {
x := 1
fmt.Println(x) //prints 1
{
fmt.Println(x) //prints 1
x := 2
fmt.Println(x) //prints 2
}
fmt.Println(x) //prints 1 (bad if you need 2)
}
即使對於經驗豐富的Go開發者而言,這也是一個非常常見的陷阱。這個坑很容易挖,但又很難發現。
不使用顯式型別,無法使用“nil”來初始化變數
- level: beginner
“nil”標誌符用於表示interface、函式、maps、slices和channels的“零值”。如果你不指定變數的型別,編譯器將無法編譯你的程式碼,因為它猜不出具體的型別。
Fails:
package main
func main() {
var x = nil //error
_ = x
}
Compile Error:
/tmp/sandbox188239583/main.go:4: use of untyped nil
Works:
package main
func main() {
var x interface{} = nil
_ = x
}
使用“nil” Slices and Maps
- level: beginner
在一個“nil”的slice中新增元素是沒問題的,但對一個map做同樣的事將會生成一個執行時的panic。
Works:
package main
func main() {
var s []int
s = append(s,1)
}
Fails:
package main
func main() {
var m map[string]int
m["one"] = 1 //error
}
Map的容量
- level: beginner
你可以在map建立時指定它的容量,但你無法在map上使用cap()函式。
Fails:
package main
func main() {
m := make(map[string]int,99)
cap(m) //error
}
Compile Error:
/tmp/sandbox326543983/main.go:5: invalid argument m (type map[string]int) for cap
字串不會為“nil”
- level: beginner
這對於經常使用“nil”分配字串變數的開發者而言是個需要注意的地方。
Fails:
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)
Works:
package main
func main() {
var x string //defaults to "" (zero value)
if x == "" {
x = "default"
}
}
Array函式的引數
-level: beginner
如果你是一個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])
}
如果你需要更新原始陣列的資料,你可以使用陣列指標型別。
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]