如何编写 Go 代码

简介

本文档演示了在模块中开发一个简单的 Go 包,并介绍了 go 工具,这是获取、构建和安装 Go 模块、包和命令的标准方式。

代码组织

Go 程序被组织成包。是同一目录中编译在一起的源文件集合。在一个源文件中定义的函数、类型、变量和常量对于同一包中的所有其他源文件都是可见的。

一个代码库包含一个或多个模块。一个 模块 是一个相关 Go 包的集合,这些包一起发布。一个 Go 代码库通常只包含一个模块,位于代码库的根目录中。一个名为 go.mod 的文件声明了 模块路径:模块中所有包的导入路径前缀。该模块包含其 go.mod 文件所在的目录中的包以及该目录的子目录,直到包含另一个 go.mod 文件的下一个子目录(如果有)。

请注意,在构建代码之前,您无需将其发布到远程代码库。一个模块可以在本地定义,而无需属于一个代码库。但是,将您的代码组织得好像您将来会发布它一样是一个好习惯。

每个模块的路径不仅作为其包的导入路径前缀,还指示 go 命令应该在哪里查找以下载它。例如,为了下载模块 golang.org/x/toolsgo 命令将咨询 https://golang.ac.cn/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 结尾的文件来编写测试,该文件包含名为 TestXXX 的函数,其签名为 func (t *testing.T)。测试框架运行每个此类函数;如果函数调用故障函数(例如 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 问题跟踪器 报告错误。