如何撰寫 Go 程式碼

簡介

此文件說明如何在模組內開發一個簡單的 Go 套件,並介紹 go 工具,這是擷取、建置和安裝 Go 模組、套件和指令的標準方式。

程式碼組織

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

一個儲存庫包含一個或多個模組。一個 模組 是相關 Go 套件的集合,會一起發布。一個 Go 儲存庫通常只包含一個模組,位於儲存庫的根目錄中。一個名為 go.mod 的檔案會在那裡宣告 模組路徑:模組中所有套件的匯入路徑前綴。模組包含其 go.mod 檔案所在的目錄中的套件,以及該目錄的子目錄,直到包含另一個 go.mod 檔案的下一個子目錄(如果有)。

請注意,您不需要將程式碼發布到遠端儲存庫才能建置它。一個模組可以在不屬於儲存庫的情況下在本地定義。但是,將您的程式碼組織得好像您有一天會發布它一樣,是一個好習慣。

每個模組的路徑不僅作為其套件的匯入路徑前綴,還表示 go 指令應該在哪裡尋找它以下載它。例如,為了下載模組 golang.org/x/toolsgo 指令會諮詢 https://go.dev.org.tw/x/tools 所指示的儲存庫(在此處 更詳細說明)。

一個 匯入路徑 是用來匯入套件的字串。套件的匯入路徑是其模組路徑與其在模組中的子目錄結合而成。例如,模組 github.com/google/go-cmpcmp/ 目錄中包含一個套件。該套件的匯入路徑是 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 結尾,並包含具有簽章 func (t *testing.T) 的函式 TestXXX。測試架構會執行每個此類函式;如果函式呼叫失敗函式,例如 t.Errort.Fail,則測試會被視為失敗。

透過建立包含以下 Go 程式碼的檔案 $HOME/hello/morestrings/reverse_test.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 程式碼的提示。

進行 Go 導覽 以正確學習此語言。

造訪 文件頁面 以取得一系列關於 Go 語言及其函式庫和工具的深入文章。

取得協助

如需即時協助,請在社群經營的 gophers Slack 伺服器 中詢問熱心的 gophers(在此處取得邀請函)。

討論 Go 語言的官方郵件清單為 Go Nuts

使用 Go 問題追蹤器 回報錯誤。