English | 繁體中文
Go / Golang
Go 是一種具備垃圾回收、明確錯誤值、結構型介面、輕量級 goroutine 與通道的編譯型靜態型別語言。偏好簡單、易讀的程式碼;執行 gofmt,並讓 go vet/測試引導重構。
工具鏈與模組
go mod init example.com/myapp # 建立 go.mod
go get example.com/lib@v1.2.3 # 新增或變更相依項
go mod tidy # 同步 go.mod/go.sum
go run . # 編譯並執行
go build ./... # 建置所有套件
go test ./... -race -cover # 測試、競態偵測器、涵蓋率
go vet ./... # 可疑的建構
gofmt -w . # 標準格式化
go doc fmt.Println # 套件/API 文件
go env GOPATH GOMOD GOVERSION一個目錄通常對應一個套件。一個模組可以包含多個套件;internal/ 會限制只能從其父系樹狀目錄匯入;cmd/ 通常包含可執行檔。套件名稱應簡短且使用小寫。匯出的識別字以大寫字母開頭,並應附有文件註解。
語法與核心型別
package main
import "fmt"
const (
MaxRetries = 3
_ = iota // iota 是無型別整數常數產生器
)
var count int = 1
name := "Go" // 短宣告;只能在函式內使用
// 零值:0、false、""、nil 指標/切片/映射/函式/介面/通道。
var (
n int
ok bool
text string
nums []int // nil 切片可以安全地進行 range 與 append
table map[string]int
)
// 內建型別:bool;有號/無號整數;float32/64;complex64/128;
// string(不可變的位元組,通常是 UTF-8);byte = uint8;rune = int32。
// 轉換必須明確指定:int64(n)、float64(n)、[]byte(text)、string(data)。
var (
a [3]int // 陣列:長度是其型別的一部分
s = []int{1, 2, 3} // 切片:指標、長度、容量
m = map[string]int{} // 映射;查找會回傳(值、是否找到)
)
s = append(s, 4)
copy(s[1:], s[:2])
for key, value := range m { _ = key; _ = value }
// 結構體將具名欄位分組;嵌入會提升欄位/方法。
type User struct {
ID int `json:"id"` // 結構標籤,由反射/套件讀取
Name string `json:"name"`
}
u := User{ID: 1, Name: "Ada"}
ptr := &u
ptr.Name = "Grace" // 選擇器會自動解參照
// 指標:*T;取址:&x;解參照:*p。Go 沒有指標算術。控制流程
if value, found := m["go"]; found {
fmt.Println(value)
} else {
fmt.Println("missing")
}
for i := 0; i < 3; i++ {} // 唯一的迴圈關鍵字
for condition() {} // while 風格
for range s {} // 迭代值/索引
switch name {
case "Go":
// 不會隱含貫穿到下一個 case
case "Rust", "C":
// 以逗號分隔的 case
default:
}
// 可使用 `break`、`continue` 以及帶標籤的 break/continue。
// `goto` 存在,但很少適用。函式、方法與介面
func divide(a, b int) (quotient int, err error) {
if b == 0 { return 0, fmt.Errorf("divide by zero") }
return a / b, nil
}
func sum(values ...int) int { // 在函式內,變動參數 values 是 []int
total := 0
for _, v := range values { total += v }
return total
}
func (u User) Label() string { return u.Name } // 值接收者
func (u *User) Rename(name string) { u.Name = name } // 指標接收者
// 介面是隱式滿足的:型別只需要具備所要求的方法。
type Stringer interface { String() string }
func printAny(v any) {
switch x := v.(type) { // 型別 switch
case string:
fmt.Println(x)
case int:
fmt.Println(x)
default:
fmt.Printf("%T\n", x)
}
}
// `any` 是 interface{} 的別名;可行時請使用更窄的介面。
// 斷言:text, ok := v.(string)。未使用 `ok` 的失敗斷言會觸發 panic。泛型
type Number interface { ~int | ~int64 | ~float64 }
func Sum[T Number](xs []T) T {
var total T
for _, x := range xs { total += x }
return total
}~int 包含底層型別為 int 的具名型別。型別參數可以由介面加以限制;泛型方法不允許引入自身的型別參數。
錯誤與資源安全
錯誤是普通值。回傳它們;使用 %w 增加上下文;使用 errors.Is/errors.As 進行檢查。
if err != nil {
return fmt.Errorf("load user %d: %w", id, err)
}
if errors.Is(err, context.Canceled) { /* 取消是預期情況 */ }
var pathErr *os.PathError
if errors.As(err, &pathErr) { /* 處理具型別的錯誤 */ }取得資源後應立即使用 defer:defer file.Close()、defer mu.Unlock()。延遲呼叫會以 LIFO 順序執行,並擷取 defer 陳述式當下的引數。panic 用於不可恢復的程式設計錯誤/不變量違反,而非例行驗證。recover 只有在同一個 goroutine 的 deferred 函式中才有效。
對不受信任的檔案系統路徑,請在邊界進行驗證(filepath.IsLocal、filepath.Localize);在 Go 1.24 及更新版本中,也可考慮使用 os.OpenRoot/Root.OpenInRoot,將存取限制在某個目錄內。
並行
go f(x)會在 goroutine 中啟動f;它與呼叫者共享記憶體。- 通道會傳遞值並進行同步:
make(chan T)、具緩衝的make(chan T, n)。 - 通常由傳送者關閉通道;從已關閉的通道接收時會得到零值,且
ok == false。 select會等待一個已就緒的通道操作;case <-ctx.Done()用於處理取消。- 第一次使用後絕不要複製
sync.Mutex。保護共享狀態,或使用通道/原子操作。 sync.WaitGroup會等待 goroutine 加入;sync.Once只初始化一次;sync.Pool用於暫時可重用的物件;sync.Map是專門用途,不是預設的映射替代品。- 小型獨立計數器/狀態請使用
sync/atomic。執行go test -race ./...;競態是正確性錯誤。 - 每個 goroutine 都需要終止路徑。將
context.Context作為第一個參數,取消衍生的 context,並避免 goroutine/通道洩漏。 - nil 通道會永久阻塞;將通道變數設為 nil 可以停用
selectcase。向已關閉的通道傳送會 panic;重複關閉會 panic。select { default: }是非阻塞的。
可編譯範例:可取消的 worker pool、錯誤、泛型與 JSON
將其儲存為模組中的 main.go,然後執行 go run .(適用於目前的 Go 版本)。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
)
type Job struct{ ID int; Text string }
type Result struct{ ID int; Length int; Err error }
// 泛型輔助函式:適用於任何元素型別。
func Map[T, U any](xs []T, f func(T) U) []U {
out := make([]U, len(xs))
for i, x := range xs { out[i] = f(x) }
return out
}
func worker(ctx context.Context, jobs <-chan Job, results chan<- Result, processed *atomic.Int64, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case job, ok := <-jobs:
if !ok { return }
if job.Text == "" {
results <- Result{ID: job.ID, Err: errors.New("empty text")}
continue
}
// 模擬 I/O,同時保持可取消。
select {
case <-ctx.Done():
return
case <-time.After(1 * time.Millisecond):
}
processed.Add(1)
results <- Result{ID: job.ID, Length: len([]rune(job.Text))}
}
}
}
func run(ctx context.Context, jobs []Job, workerCount int) ([]Result, int64, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
jobCh := make(chan Job)
resultCh := make(chan Result)
var processed atomic.Int64
var wg sync.WaitGroup
wg.Add(workerCount)
for i := 0; i < workerCount; i++ {
go worker(ctx, jobCh, resultCh, &processed, &wg)
}
go func() {
defer close(jobCh)
for _, job := range jobs {
select {
case jobCh <- job:
case <-ctx.Done():
return
}
}
}()
go func() {
wg.Wait()
close(resultCh)
}()
results := make([]Result, 0, len(jobs))
for result := range resultCh {
results = append(results, result)
if result.Err != nil {
cancel() // 第一個錯誤工作出現後停止剩餘工作
}
}
for _, result := range results {
if result.Err != nil {
return results, processed.Load(), fmt.Errorf("job %d: %w", result.ID, result.Err)
}
}
return results, processed.Load(), nil
}
func main() {
jobs := []Job{{1, "gopher"}, {2, "並發"}, {3, ""}}
results, processed, err := run(context.Background(), jobs, 2)
payload, marshalErr := json.Marshal(results)
if marshalErr != nil { panic(marshalErr) }
fmt.Printf("processed=%d results=%s err=%v\n", processed, payload, err)
lengths := Map([]string{"go", "is", "fun"}, func(s string) int { return len(s) })
fmt.Println("lengths:", lengths)
}此範例展示了只接收/只傳送的通道型別、取消、通道所有權/關閉、WaitGroup、原子狀態、泛型函式、多重回傳值、包裝錯誤、Unicode rune 計數與 JSON。它會刻意將空工作回報為錯誤,同時乾淨地關閉 worker。
標準函式庫索引
- 文字/資料:
strings、bytes、strconv、unicode/utf8、encoding/json、encoding/csv、regexp。 - 檔案/路徑:
io、os、path/filepath、bufio、embed。 - HTTP/網路:
net/http、net/url、net、httptest;務必為用戶端/伺服器設定逾時。 - 時間/context:
time、context;在呼叫鏈中傳遞 context,不要將其儲存在結構體中。 - 集合/演算法:
sort、slices、maps、container/heap。 - 系統/並行:
sync、sync/atomic、runtime、os/signal。 - 可觀測性:
log/slog、expvar、runtime/pprof、net/http/pprof。
測試、效能與設計檢查清單
func TestSum(t *testing.T) {
got := Sum([]int{1, 2, 3})
if got != 6 { t.Fatalf("got %d, want 6", got) }
}使用表格驅動測試、子測試(t.Run)、基準測試(go test -bench .)、模糊測試(go test -fuzz=.)以及用於 HTTP 的 httptest。測試公開行為時,偏好黑箱套件測試(package x_test)。最佳化前先進行基準測試;使用 -benchmem、pprof 與配置檔分析。避免不必要的配置;重複建構字串時使用 strings.Builder,但應先以清晰易讀為優先。
慣用法: 使用由使用者擁有的小型介面;接受介面並回傳具體型別;以組合/嵌入取代繼承;讓建構函式驗證不變量;讓零值可用;避免全域可變狀態;為匯出的 API 撰寫文件;使用 defer 進行清理;除非有刻意且充分的理由,絕不忽略錯誤;明確管理 goroutine 的所有權與關閉。