如何編寫 Go 程式碼

引言

本文演示瞭如何在模組中開發一個簡單的 Go 包,並介紹了 go tool,它是獲取、構建和安裝 Go 模組、包和命令的標準方法。

程式碼組織

Go 程式被組織成包。一個 是同一個目錄中一起編譯的原始檔集合。在一個原始檔中定義的函式、型別、變數和常量對同一包中的所有其他原始檔都可見。

一個倉庫包含一個或多個模組。一個 模組 是相關 Go 包的集合,它們一起釋出。一個 Go 倉庫通常只包含一個模組,位於倉庫的根目錄。在那裡,一個名為 go.mod 的檔案聲明瞭 模組路徑:模組中所有包的匯入路徑字首。該模組包含其 go.mod 檔案所在目錄中的包以及該目錄的子目錄中的包,直到下一個包含另一個 go.mod 檔案的子目錄(如果有)。

請注意,您無需在構建程式碼之前將其釋出到遠端倉庫。模組可以在本地定義,而無需屬於某個倉庫。然而,將程式碼組織起來,就像將來會發布一樣,是一個好習慣。

每個模組的路徑不僅作為其包的匯入路徑字首,還指示 go 命令應在哪裡查詢以下載它。例如,為了下載模組 golang.org/x/toolsgo 命令會查詢 https://golang.org.tw/x/tools 所指示的倉庫(更多描述請參見 此處)。

一個 匯入路徑 是一個用於匯入包的字串。包的匯入路徑是其模組路徑與模組內子目錄的組合。例如,模組 github.com/google/go-cmp 在目錄 cmp/ 中包含一個包。該包的匯入路徑是 github.com/google/go-cmp/cmp。標準庫中的包沒有模組路徑字首。

您的第一個程式

要編譯和執行一個簡單的程式,首先選擇一個模組路徑(我們將使用 example/user/hello),然後建立一個宣告該路徑的 go.mod 檔案

$ mkdir hello # Alternatively, clone it if it already exists in version control.
$ cd hello
$ go mod init example/user/hello
go: creating new go.mod: module example/user/hello
$ cat go.mod
module example/user/hello

go 1.16
$

Go 原始檔中的第一條語句必須是 package name。可執行命令必須始終使用 package main

接下來,在該目錄中建立一個名為 hello.go 的檔案,其中包含以下 Go 程式碼

package main

import "fmt"

func main() {
    fmt.Println("Hello, world.")
}

現在您可以使用 go 工具構建並安裝該程式

$ go install example/user/hello
$

此命令構建 hello 命令,生成一個可執行二進位制檔案。然後它將該二進位制檔案安裝為 $HOME/go/bin/hello(或者,在 Windows 下,%USERPROFILE%\go\bin\hello.exe)。

安裝目錄由 GOPATHGOBIN 環境變數 控制。如果設定了 GOBIN,則二進位制檔案將安裝到該目錄。如果設定了 GOPATH,則二進位制檔案將安裝到 GOPATH 列表中第一個目錄的 bin 子目錄。否則,二進位制檔案將安裝到預設 GOPATH$HOME/go%USERPROFILE%\go)的 bin 子目錄。

您可以使用 go env 命令為未來的 go 命令可移植地設定環境變數的預設值

$ go env -w GOBIN=/somewhere/else/bin
$

要取消設定之前透過 go env -w 設定的變數,請使用 go env -u

$ go env -u GOBIN
$

go install 這樣的命令在包含當前工作目錄的模組的上下文中適用。如果工作目錄不在 example/user/hello 模組中,go install 可能會失敗。

為方便起見,go 命令接受相對於工作目錄的路徑,如果沒有給出其他路徑,則預設為當前工作目錄中的包。因此,在我們的工作目錄中,以下命令都是等效的

$ go install example/user/hello
$ go install .
$ go install

接下來,讓我們執行程式以確保它正常工作。為了方便起見,我們將安裝目錄新增到 PATH 中,以便輕鬆執行二進位制檔案

# Windows users should consult /wiki/SettingGOPATH
# for setting %PATH%.
$ export PATH=$PATH:$(dirname $(go list -f '{{.Target}}' .))
$ hello
Hello, world.
$

如果您正在使用原始碼管理系統,現在是初始化倉庫、新增檔案並提交第一次更改的好時機。再次強調,此步驟是可選的:您不需要使用原始碼管理來編寫 Go 程式碼。

$ git init
Initialized empty Git repository in /home/user/hello/.git/
$ git add go.mod hello.go
$ git commit -m "initial commit"
[master (root-commit) 0b4507d] initial commit
 1 file changed, 7 insertion(+)
 create mode 100644 go.mod hello.go
$

go 命令透過請求相應的 HTTPS URL 並讀取 HTML 響應中嵌入的元資料來定位包含給定模組路徑的倉庫(請參閱 go help importpath)。許多託管服務已經為包含 Go 程式碼的倉庫提供了該元資料,因此讓您的模組可供他人使用的最簡單方法通常是使其模組路徑與倉庫的 URL 匹配。

從您的模組匯入包

讓我們編寫一個 morestrings 包並在 hello 程式中使用它。首先,為該包建立一個名為 $HOME/hello/morestrings 的目錄,然後在該目錄中建立一個名為 reverse.go 的檔案,其中包含以下內容

// Package morestrings implements additional functions to manipulate UTF-8
// encoded strings, beyond what is provided in the standard "strings" package.
package morestrings

// ReverseRunes returns its argument string reversed rune-wise left to right.
func ReverseRunes(s string) string {
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

因為我們的 ReverseRunes 函式以大寫字母開頭,所以它是 匯出 的,可以在匯入我們 morestrings 包的其他包中使用。

讓我們用 go build 測試包是否編譯

$ cd $HOME/hello/morestrings
$ go build
$

這不會產生輸出檔案。相反,它將編譯後的包儲存在本地構建快取中。

在確認 morestrings 包可以構建之後,讓我們在 hello 程式中使用它。為此,修改您原來的 $HOME/hello/hello.go 以使用 morestrings 包

package main

import (
    "fmt"

    "example/user/hello/morestrings"
)

func main() {
    fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
}

安裝 hello 程式

$ go install example/user/hello

執行新版本的程式,您應該會看到一條新的、反轉的訊息

$ hello
Hello, Go!

從遠端模組匯入包

匯入路徑可以描述如何使用版本控制系統(例如 Git 或 Mercurial)獲取包原始碼。go 工具利用此特性自動從遠端倉庫獲取包。例如,要在您的程式中使用 github.com/google/go-cmp/cmp

package main

import (
    "fmt"

    "example/user/hello/morestrings"
    "github.com/google/go-cmp/cmp"
)

func main() {
    fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
    fmt.Println(cmp.Diff("Hello World", "Hello Go"))
}

現在您依賴於一個外部模組,您需要下載該模組並將其版本記錄在您的 go.mod 檔案中。go mod tidy 命令會新增匯入包所需的缺失模組依賴項,並移除不再使用的模組依賴項。

$ go mod tidy
go: finding module for package github.com/google/go-cmp/cmp
go: found github.com/google/go-cmp/cmp in github.com/google/go-cmp v0.5.4
$ go install example/user/hello
$ hello
Hello, Go!
  string(
-     "Hello World",
+     "Hello Go",
  )
$ cat go.mod
module example/user/hello

go 1.16

require github.com/google/go-cmp v0.5.4
$

模組依賴項會自動下載到 GOPATH 環境變數指示的目錄的 pkg/mod 子目錄中。給定版本模組的下載內容由所有 require 該版本的其他模組共享,因此 go 命令將這些檔案和目錄標記為只讀。要刪除所有已下載的模組,您可以將 -modcache 標誌傳遞給 go clean

$ go clean -modcache
$

測試

Go 有一個輕量級的測試框架,由 go test 命令和 testing 包組成。

您透過建立一個名稱以 _test.go 結尾的檔案來編寫測試,該檔案包含名為 TestXXX 且簽名為 func (t *testing.T) 的函式。測試框架會執行每個這樣的函式;如果該函式呼叫了失敗函式,例如 t.Errort.Fail,則測試被認為是失敗的。

透過建立檔案 $HOME/hello/morestrings/reverse_test.go,其中包含以下 Go 程式碼,為 morestrings 包新增一個測試。

package morestrings

import "testing"

func TestReverseRunes(t *testing.T) {
    cases := []struct {
        in, want string
    }{
        {"Hello, world", "dlrow ,olleH"},
        {"Hello, 世界", "界世 ,olleH"},
        {"", ""},
    }
    for _, c := range cases {
        got := ReverseRunes(c.in)
        if got != c.want {
            t.Errorf("ReverseRunes(%q) == %q, want %q", c.in, got, c.want)
        }
    }
}

然後使用 go test 執行測試

$ cd $HOME/hello/morestrings
$ go test
PASS
ok  	example/user/hello/morestrings 0.165s
$

執行 go help test 並檢視 testing 包文件 以獲取更多詳細資訊。

下一步

訂閱 golang-announce 郵件列表,以便在新穩定版 Go 釋出時收到通知。

參閱 Effective Go,獲取編寫清晰、地道的 Go 程式碼的技巧。

參加 A Tour of Go 學習語言本身。

訪問 文件頁面,獲取一系列關於 Go 語言及其庫和工具的深入文章。

獲取幫助

如需即時幫助,請在社群運營的 gophers Slack 伺服器 上向樂於助人的 gophers 提問(在此獲取邀請)。

討論 Go 語言的官方郵件列表是 Go Nuts

使用 Go issue tracker 報告錯誤。