```markdown

简介

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

代码组织

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

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

请注意,你不需要在构建代码之前将其发布到远程仓库。一个模块可以在本地定义,而不属于某个代码仓库。不过,养成像将来某天会发布代码那样来组织代码的习惯是很好的。

每个模块的路径不仅用作其包的导入路径前缀,还指示了go命令应去哪里下载它。例如,为了下载模块golang.org/x/toolsgo命令会查阅https://golang.org/x/tools指示的仓库(更多描述见这里)。

一个导入路径是一个用于导入包的字符串。一个包的导入路径是其模块路径与其在模块内的子目录路径连接而成。例如,模块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

接下来,在该目录下创建一个名为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 用户应参阅 /wiki/SettingGOPATH
# 来设置 %PATH%。
$ export PATH=$PATH:$(dirname $(go list -f '{{"{{.Target}}"}}' .))
$ hello
Hello, world.
$

如果你正在使用源代码控制系统,现在将是初始化仓库、添加文件并提交第一个更改的好时机。再次强调,这一步是可选的:你不需要使用源代码控制来编写Go代码。

```
$ git init
已初始化空的 Git 仓库于 /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 提供了超出标准 "strings" 包之外的,
// 用于操作 UTF-8 编码字符串的附加函数。
package morestrings

// ReverseRunes 返回其参数字符串逐个 rune(Unicode 码点)反转后的结果。
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 代码的技巧。

参加 Go语言之旅 ,学习该语言的规范用法。

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

获取帮助

如需实时帮助,可在社区运营的 Gophers Slack 服务器 中向热心的 Gophers 请教(点击此处获取邀请链接)。

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

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