如何编写 Go 代码

介绍

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

注意: 本文档假定您使用的是 Go 1.13 或更高版本,并且未设置 GO111MODULE 环境变量。如果您正在寻找本文档较旧的预模块版本,它在此处存档。

代码组织

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

一个存储库包含一个或多个模块。一个模块是一起发布的相关 Go 包的集合。一个Go 存储库通常只包含一个模块,位于存储库的根目录下。那里有一个名为go.mod的文件声明模块路径:模块中所有包的导入路径前缀。The module contains the packages in the directory containing its go.mod file as well as subdirectories of that directory, up to the next subdirectory containing another go.mod file (if any).该模块包含包含其 go.mod 文件的目录中的包以及该目录的子目录,直至包含另一个 go.mod 文件的下一个子目录(如果有)。

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

每个模块的路径不仅用作其包的导入路径前缀,而且还指示 go 命令应查找下载它的位置。例如,为了下载模块 golang.org/x/toolsgo命令将查询https://golang.org/x/tools 指示的存储库(此处有更多介绍)。

import path是用于导入包的字符串。包的导入路径是其模块路径与其在模块内的子目录相连。例如,模块 github.com/google/go-cmp 包含 cmp/ 目录中的包。该包的导入路径 是github.com/google/go-cmp/cmp。标准库中的包没有模块路径前缀。

你的第一个程序

要编译并运行一个简单的程序,首先选择一个模块路径(我们将使用 example/user/hello),然后创建一个声明它的 go.mod文件:

$ mkdir hello # 或者,如果它已经存在于版本控制中,则克隆它.
$ 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.

接下来,在该目录中创建一个包含以下 Go 代码的文件hello.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 子目录中。否则,二进制文件将安装到默认 GOPATHbin 子目录($HOME/go%USERPROFILE%\go).

您可以使用 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 用户应参考 https://github.com/golang/go/wiki/SettingGOPATH
# 设置 %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 的文件,其中包含以下内容:

//  包 morestrings 实现了额外的函数来操作 UTF-8
// 编码的字符串,超出了标准“字符串”包中提供的功能.
package morestrings

// ReverseRunes 返回其从左到右逆符文的参数字符串.
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并查看测试包文档以获取更多详细信息.

下一步是什么

订阅 golang-announce 邮件列表,以便在发布新的稳定版 Go 时收到通知.

有关编写清晰、惯用的 Go 代码的技巧,请参阅Effective Go.

参加 Go边学边练 以学习正确的语言编程方式.

访问文档页面,获取一组关于 Go 语言及其库和工具的深入文章。

获得帮助

如需实时帮助,请在社区运行的gophers Slack server中询问可提供帮助的 gophers (在此处获取邀请)。

讨论 Go 语言的官方邮件列表是 Go Nuts.

使用Go 问题跟踪器报告错误。