Categories
程式開發

翻译:如何编写Golang代码(How to Write Go Code)


本文是Golang官方文档How to Write Go Code“的翻译。由于众所周知的原因,国内是打不开golang.org的,而百度搜索的Golang教程良莠不齐,很多都有错误。尤其当Go 1.14发布后,基于Gopath的组织方式已经过期了,取而代之的是Mudule。因为网上缺少此类的教程,所以特翻译官方文档,供自己回顾也供大家学习。勘误请发邮件。

一些关键词汇

单词采取的翻译module模块package包fetch获取build构建install安装function方法type类型repository库

介绍

本文演示了一个包括模块(module)的简单Go包的开发,并介绍了Go的一些工具,这是获取(fetch),构建(build)和安装(install) Go模块,包和命令的标准方式。

注意:本文档假定读者使用的是Go 1.13或更高版本,并未设置Go111MODULE环境变量。如果在寻找老版本的使用方法,可以点击此处

代码的组织

Go程序由包进行管理。包是同一个目录中被一起编译的源文件的集合。在一个源文件中定义的方法,类型,变量和常量对于包内所有其他源文件都是可见的。

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

需要注意的是,在构建代码之前,无需将代码发布到远程仓库。可以在本地定义一个不属于库的模块,但是,未来总是会发布代码的,所以提前组织好自己的代码是一个好习惯。

每个模块的路径不仅充当了其他包的导入路径前缀,而且还表明了go command下载该模块的地方。例如,为了下载模块 golang.org/x/tools,go command会查询https://golang.org/x/tools所在的库。

导入路径是用来导入包的字符串。包的导入路径由其模块路径加上其所在模块中的子目录组成。例如,模块github.com/google/go-cmp包含了在目录*cmp/*中的一个包,那么该包的导入路径为:github.com/google/go-cmp/cmp. 标准库中的包没有模块路径前缀。

第一个程序

为了编译和执行一个简单的程序,首先需要选择一个模块路径(我们将会使用example.com/user/hello),然后创建一个声明模块的go.mod文件。(译者注:以下操作需要打开终端,均为终端命令)

$ mkdir hello
$ cd hello
$ go mod init example.com/user/hello
go: creating new go.mod: module example.com/user/hello
$ cat go.mod
module example.com/user/hello

go 1.14
$

Go 源文件的第一句语句必须是包名。可执行命令则必须使用package main.

接下来,在该目录下创建一个hello.go文件,其中包含以下代码:

package main

import "fmt"

func main() {
fmt.Println("Hello World.")
}

现在可以使用go tool构建并安装这个程序了。

$ go install example.com/user/hello

这个命令会生成hello命令,生成一个可执行二进制文件,然后将该二进制文件安装为$HOME/go/bin/hello(如果是Windows,则为:%USERPROFILE%gobinhello.exe).

这个安装目录由GOPATH和GOBIN环境变量进行控制。如果设置了GOBIN,则二进制文件将安装到此目录,如果设置了GOPATH,二进制将安装到GOPATH列表中第一个目录的bin子目录中。否则,二进制文件将默认安装到到GOPATH的bin子目录。($HOME/go 或%USERPROFILE%go)

你可以使用go env命令去设置默认的环境变量:

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

要取消先前由go env -w设置的变量,可以使用go env -u:

$ go env -u GOBIN

诸如go install 之类的命令适用于包含当前工作目录的模块的上下文,如果工作目录不在example.com/user/hello模块内,则安装可能会失败。

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

go install example.com/user/hello

go install .

go install

接下来,让我们运行该程序以确保其能正常工作。为了更加方便,我们将安装目录添加到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

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.com/user/hello/morestrings"
)

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

安装hello程序

$ go install example.com/user/hello

运行新版本的hello程序,你应该可以见到新的反转过后的消息:

$ hello
Hello, Go!

从远程模块导入包

导入路径可以描述如何去Git或Mercuial等版本控制系统获取包的源码。Go tool使用此属性自动的从远程仓库获取包。例如,要在程序中使用github.comn/google/go-cmp/cmp:

package main

import (
"fmt"

"example.com/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 install, go build,或者go run等命令时,go命令会自动下载远程模块,并将其版本记录在go.mod文件中:

$ go install example.com/user/hello
go: finding module for package github.com/google/go-cmp/cmp
go: downloading github.com/google/go-cmp v0.4.0
go: found github.com/google/go-cmp/cmp in github.com/google/go-cmp v0.4.0
$ hello
Hello, Go!
string(
- "Hello World",
+ "Hello Go",
)
$ cat go.mod
module example.com/user/hello

go 1.14

require github.com/google/go-cmp v0.4.0
$

模块的依赖会自动下载到GOPATH环境变量设置的目录pkg/mod子目录中。给定版本的模块的下载内容在需要该版本的所有其他模块之间共享,因此go命令将这些文件和目录标记为只读。要删除所有下载的模块,可以使用go clean -modcache

$ go clean -modcache`
$

测试

Go有一个由go test命令和testing包组成的轻量级测试框架。

你可以通过创建一个名称以_test.go结尾的文件来编写测试,文件里包含名为TestXXX且具有签名func(t testing.T)的方法。测试框架会运行每一个这样的方法,如果该方法的调用结果为t.Error或t.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运行这个测试:

$ go test
PASS
ok example.com/user/morestrings 0.165s
$

运行go help test 或查阅testing 包文档可以获得更多的细节信息。

接下来该看什么

订阅golang的公告邮件,以便在发布新的稳定版时收到通知。有关编写清晰,惯用的Go代码可以参见Effective Go。参见 A Tour of Go去学习基础语法。访问文档中心获取有关Go语言以及库和工具的更多文章。