如何编写 Go 代码

How to Write Go Code - 如何编写 Go 代码

原文:https://go.dev/doc/code

简介

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

注意:本文档假设您使用的是Go 1.13或更高版本,并且没有设置GO111MODULE环境变量。如果您要找的是本文档的旧版,即模块之前的版本,它被存档在这里

代码组织

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

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

​ 请注意,您不需要在构建之前将您的代码发布到一个远程仓库。一个模块可以在本地定义而不属于一个版本库。然而,组织您的代码是一个好习惯,就像您有一天会发布它一样。

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

​ 导入路径是一个用于导入软件包的字符串。一个包的导入路径是它的模块路径与模块中的子目录相连接。例如,模块github.com/google/go-cmpcmp/目录下包含一个包。该包的导入路径是github.com/google/go-cmp/cmp标准库中的包没有模块路径前缀

您的第一个程序

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

1
2
3
4
5
6
7
8
9
$ 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代码:

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
    fmt.Println("Hello, world.")
}

现在您可以用go工具构建和安装该程序:

1
2
$ go install example/user/hello
$

​ 这个命令构建了hello命令,产生了一个可执行的二进制文件。然后,它将该二进制文件安装为$HOME/go/bin/hello(或者,在Windows下,%USERPROFILE%\gobinhello.exe)。

​ 安装目录是由GOPATHGOBIN环境变量控制的。如果设置了GOBIN,二进制文件将被安装到该目录。如果设置了GOPATH,二进制文件将被安装到GOPATH列表中第一个目录的bin子目录中。否则,二进制文件将被安装到默认的 GOPATH$HOME/go%USERPROFILE%\go)的 bin 子目录。

​ 您可以使用go env命令为未来的go命令可移植地设置环境变量的默认值:

1
2
$ go env -w GOBIN=/somewhere/else/bin
$

​ 要取消先前由go env -w设置的变量,请使用go env -u

1
2
$ go env -u GOBIN
$

​ 像go install这样的命令在包含当前工作目录的模块上下文中应用。如果工作目录不在example/user/hello模块内,go install可能会失败。

​ 为了方便起见,go命令接受相对于工作目录的路径,如果没有给出其他路径,则默认为当前工作目录下的软件包。因此在我们的工作目录中,以下命令都是等价的。

1
2
3
$ go install example/user/hello
$ go install .
$ go install

​ 接下来,让我们运行该程序,以确保它能工作。为了方便起见,我们将安装目录添加到我们的PATH中,以使运行二进制文件更加容易:

1
2
3
4
5
6
# Windows users should consult https://github.com/golang/go/wiki/SettingGOPATH
# for setting %PATH%.
$ export PATH=$PATH:$(dirname $(go list -f '{{.Target}}' .))
$ hello
Hello, world.
$

​ 如果您使用的是源码控制系统,现在是初始化代码库的好时机,添加文件,并提交您的第一个改动。同样,这一步是可选的:您不需要使用源码控制来编写Go代码。

1
2
3
4
5
6
7
8
$ 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的文件,内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 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来测试一下这个包的编译情况:

1
2
3
$ cd $HOME/hello/morestrings
$ go build
$

​ 这不会产生一个输出文件。相反,它会把编译好的包保存在本地的构建缓存中。

​ 在确认了morestrings包的构建之后,让我们在hello程序中使用它。要做到这一点,请修改您原来的$HOME/hello/hello.go以使用morestrings包:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import (
    "fmt"

    "example/user/hello/morestrings"
)

func main() {
    fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
}

安装hello程序:

1
$ go install example/user/hello

运行新版本的程序,您应该看到一个新的、反转的信息:

1
2
$ hello
Hello, Go!

从远程模块导入包

​ 导入路径可以描述如何使用GitMercurial等修订控制系统获得软件包的源代码。go工具使用这个属性来自动从远程代码库获取包。例如,要在您的程序中使用github.com/google/go-cmp/cmp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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命令为导入的软件包添加缺少的模块需求,并删除不再使用的模块需求。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ 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命令将这些文件和目录标记为只读。要删除所有下载的模块,您可以在go clean中传递-modcache标志:

1
2
$ 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包添加一个测试。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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运行该测试:

1
2
3
4
5
$ cd $HOME/hello/morestrings
$ go test
PASS
ok  	example/user/hello/morestrings 0.165s
$

运行go help test,更多细节请看testing package documentation

下一步

​ 订阅 golang-announce 邮件列表,以便在 Go 的新稳定版本发布时获得通知。

​ 请参阅 Effective Go 以了解编写清晰、简洁的 Go 代码的技巧。

​ 参加 A Tour of Go来学习这门语言。

​ 访问文档页面,了解有关 Go 语言及其库和工具的一系列深度文章。

获得帮助

​ 要获得实时帮助,请向社区管理的 gophers Slack server在此获取邀请)中愿意帮忙的 gophers 咨询。

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

​ 使用 Go issue tracker报告 bugs。

最后修改 February 26, 2024: 更新 (3def5a9)