简介
本文档演示了一个简单 Go 包的开发,并介绍了 go 工具、获取、构建和安装 Go 包和命令的标准方法。
go 工具要求您以特定的方式组织代码。请仔细阅读本文档。它解释了启动和运行 Go 安装的最简单方法。
代码结构
概述
- Go 程序员通常将所有 Go 代码保存在一个工作区中。
- 工作区包含许多版本控制存储库(例如,由 Git 管理)。
- 每个存储库都包含一个或多个包。
- 每个包由单个目录中的一个或多个 Go 源文件组成。
- 包目录的路径决定了其导入路径。
Note:这与其他编程环境不同,在其他编程环境中,每个项目都有一个单独的工作区,并且工作区与版本控制存储库紧密相关。
工作区
工作区是一个目录层次结构,其根目录有两个目录:
src
包含Go源文件。bin
包含可执行命令。
go
工具构建并安装二进制文件到 bin 目录。
src
子目录通常包含多个版本控制存储库(例如 Git 或 Mercurial),用于跟踪一个或多个源包的开发。
为了让您对实际工作空间的外观有一个大致的了解,这里有一个示例:
bin/
hello # command executable
outyet # command executable
src/
golang.org/x/example/
.git/ # Git repository metadata
hello/
hello.go # command source
outyet/
main.go # command source
main_test.go # test source
stringutil/
reverse.go # package source
reverse_test.go # test source
golang.org/x/image/
.git/ # Git repository metadata
bmp/
reader.go # package source
writer.go # package source
... (many more repositories and packages omitted) ...
上面的树显示了一个包含两个存储库(示例和图像)的工作区。示例存储库包含两个命令(hello和outyet)和一个库(stringutil)。图像存储库包含bmp包和其他几个包。
典型的工作区包含许多源代码存储库,其中包含许多包和命令。大多数 Go 程序员将所有 Go 源代码和依赖项保存在一个工作区中。
Note: 不应使用符号链接将文件或目录链接到您的工作区。
命令和库由不同类型的源包构建而成。我们稍后会讨论它们的区别。
GOPATH
环境变量
GOPATH
环境变量指定了工作区的位置。它默认为主目录中名为 go
的目录,因此在 Unix上为 $HOME/go
,在Plan 9 上为$home/go
,在Windows 上为 %USERPROFILE%\go
(通常为 C:\Users\YourName\go
)。
如果您想在其他位置工作,则需要将 GOPATH 设置为该目录的路径。(另一种常见设置是设置 GOPATH=$HOME
。)请注意,GOPATH
不能与您的 Go 安装路径相同(GOROOT
)。
为了方便起见,将工作区的 bin 子目录添加到您的PATH
:
$ export PATH=$PATH:$(go env GOPATH)/bin
为了简洁起见,本文档其余部分的脚本使用$GOPATH
而不是 $(go env GOPATH)
。如果您尚未设置 GOPATH,要使脚本按编写的方式运行,您可以在这些命令中替换 $HOME/go,或者运行:
$ export GOPATH=$(go env GOPATH)
导入路径
导入路径是唯一标识包的字符串。包的导入路径对应于其在工作区或远程存储库中的位置(如下所述)。
标准库中的软件包具有简短的导入路径,例如"fmt"
和"net/http"
。对于您自己的软件包,您必须选择一个不太可能与将来添加到标准库或其他外部库中的内容发生冲突的基本路径。
如果您将代码保存在某个源存储库中,则应使用该源存储库的根目录作为基本路径。例如,如果您在 github.com/user
有一个 GitHub 帐户,则这应该是您的基本路径。
请注意,您无需先将代码发布到远程存储库,然后才能构建它。组织代码就像将来发布一样,这只是一个好习惯。实际上,您可以选择任意路径名,只要它对于标准库和更大的 Go 生态系统是唯一的即可。
我们将使用 github.com/user
作为基本路径。在工作区内创建一个目录来保存源代码:
$ mkdir -p $GOPATH/src/github.com/user
你的第一个程序
要编译和运行一个简单的程序,首先选择一个包路径(我们将使用 github.com/user/hello
)并在工作区内创建一个相应的包目录:
$ mkdir $GOPATH/src/github.com/user/hello
接下来,在该目录中创建一个名为 hello.go
的文件,其中包含以下 Go 代码。
package main
import "fmt"
func main() {
fmt.Println("Hello, world.")
}
现在你可以使用 go
工具构建和安装该程序:
$ go install github.com/user/hello
您可以在系统的任何位置运行此命令。go
工具通过在 GOPATH
指定的工作区内查找github.com/user/hello
包来查找源代码。
如果从包目录运行 go install
,也可以省略包路径:
$ cd $GOPATH/src/github.com/user/hello
$ go install
此命令构建 hello
命令,生成可执行二进制文件。然后,它将该二进制文件安装到工作区的bin
目录中,文件名为hello
(在Windows
下为 hello.exe
)。在我们的示例中,该文件为 $GOPATH/bin/hello
,即 $HOME/go/bin/hello
。
go
工具仅在发生错误时才会打印输出,因此如果这些命令没有产生输出,则表示它们执行成功。
您现在可以通过在命令行中输入其完整路径来运行该程序:
$ $GOPATH/bin/hello
Hello, world.
或者,由于您已将 $GOPATH/bin
添加到PATH
,只需输入二进制文件名称:
$ hello
Hello, world.
如果您正在使用源代码控制系统(强烈建议),那么现在是初始化存储库、添加文件并提交第一个更改的好时机。同样,此步骤是可选的:您不需要使用源代码控制来编写 Go 代码。
$ cd $GOPATH/src/github.com/user/hello
$ git init
Initialized empty Git repository in /home/user/go/src/github.com/user/hello/.git/
$ git add hello.go
$ git commit -m "initial commit"
[master (root-commit) 0b4507d] initial commit
1 file changed, 7 insertion(+)
create mode 100644 hello.go
你的第一个library
让我们编写一个库并从hello
程序中使用它。
再次,第一步是选择一个包路径(我们将使用 github.com/user/stringutil
)并创建包目录:
$ mkdir $GOPATH/src/github.com/user/stringutil
接下来,在该目录中创建一个名为reverse.go
的文件,其内容如下。
// Package stringutil contains utility functions for working with strings.
package stringutil
// Reverse returns its argument string reversed rune-wise left to right.
func Reverse(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)
}
现在,尝试使用 go build
编译包:
或者,如果您在包的源目录中执行,只需:
$ go build
这不会产生输出文件。相反,它会将编译后的包保存在本地构建缓存中。
确认 stringutil
包构建后,修改原始 hello.go
(位于 $GOPATH/src/github.com/user/hello
)以使用它:
package main
import (
"fmt"
"github.com/user/stringutil"
)
func main() {
fmt.Println(stringutil.Reverse("!oG ,olleH"))
}
Install the hello
program:
$ go install github.com/user/hello
运行新版本的程序,您应该会看到一条新的反转消息:
$ hello
Hello, Go!
完成上述步骤后,你的工作区应如下所示:
bin/
hello # command executable
src/
github.com/user/
hello/
hello.go # command source
stringutil/
reverse.go # package source
包名
Go 源文件中的第一条语句必须是
package name
其中 name
是包的默认导入名称。(包中的所有文件必须使用相同的名称。)
Go 的惯例是包名称是导入路径的最后一个元素:作为“crypto/rot13”导入的包应该命名为 rot13。
可执行命令必须始终使用 package main。
不要求单个二进制文件引用的所有包名称都是唯一的,只需导入路径(其完整文件名)是唯一的。
测试
Go有一个轻量级的测试框架,由go test
命令和testing
包组成。
编写测试时,可以创建一个以_test.go
结尾的文件,其中包含名为TestXXX
且签名为func (t *testing.T)
的函数。测试框架会运行每个此类函数;如果该函数调用失败函数(例如 t.Error
或 t.Fail
),则认为测试失败。
通过创建包含以下 Go 代码的文件 $GOPATH/src/github.com/user/stringutil/reverse_test.go
向 stringutil
包添加测试。
package stringutil
import "testing"
func TestReverse(t *testing.T) {
cases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{"Hello, 世界", "界世 ,olleH"},
{"", ""},
}
for _, c := range cases {
got := Reverse(c.in)
if got != c.want {
t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want)
}
}
}
然后使用 go test
运行测试:
$ go test github.com/user/stringutil
ok github.com/user/stringutil 0.165s
与往常一样,如果您在包目录中运行 go 工具,则可以省略包路径:
$ go test
ok github.com/user/stringutil 0.165s
运行 go help test
并查看测试包文档以了解更多详细信息。
远端包
导入路径可以描述如何使用版本控制系统(例如 Git 或 Mercurial)获取包源代码。go
工具使用这个特性自动从远程存储库获取包。 例如,本文档中描述的示例也保存在托管于 GitHub golang.org/x/example
的 Git 存储库中。 如果您在包的导入路径中包含存储库 URL, go get 将自动获取、构建和安装它:
$ go get golang.org/x/example/hello
$ $GOPATH/bin/hello
Hello, Go examples!
如果指定的包不在工作区中,go get 会将其放置在 GOPATH 指定的第一个工作区内。(如果包已经存在,go get 将跳过远程获取,其行为与 go install 相同。)
发出上述 go get 命令后,工作区目录树现在应如下所示:
bin/
hello # command executable
src/
golang.org/x/example/
.git/ # Git repository metadata
hello/
hello.go # command source
stringutil/
reverse.go # package source
reverse_test.go # test source
github.com/user/
hello/
hello.go # command source
stringutil/
reverse.go # package source
reverse_test.go # test source
GitHub 上托管的 hello 命令依赖于同一存储库中的 stringutil 包。hello.go 文件中的导入使用相同的导入路径约定,因此 go get 命令也能够找到并安装依赖包。
import "golang.org/x/example/stringutil"
此约定是让其他人可以使用您的 Go 软件包的最简单方法。Pkg.go.dev 和 Go Wiki 提供了外部 Go 项目列表。