如何编写Go代码

介绍

这篇文档将演示如何在模块中开发一个简单的 Go 包,并介绍 Go 工具,获取、构建和安装 Go 模块和包的标准方法,以及相关的命令。

注意:这篇文档假定你使用 Go 1.13 及以上版本,并且未设置 GO111MODULE 环境变量。

代码组织

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

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

你不必在代码可以运行前就将其发布到远程仓库。因为模块可以在本地被定义,而无需属于一个代码仓库。然而,抱着你某天会发布它的态度去组织代码,无疑是一个良好的习惯。

每个模块的路径不仅作为其所含包的导入路径的前缀,而且指示 go 命令应该到哪里去下载这个模块。比如为了下载 golang.org/x/tools 模块,go 命令会查询 https://goalng.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 模块名。可执行的程序必须使用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/ 目录下(在 Windows 系统中是 %USERPROFILE%\go\bin)。

安装目录由 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

当前工作目录(working directory)包含在模块中时,像 go install 这样的命令可以正常运行。如果工作目录不在 example/user/hello 模块内,go install 命令可能会执行失败。

go 命令接受工作目录的相对路径;如果没有给出路径,默认为当前工作目录下的包。所以在我们的工作目录中,以下命令效果一致:

$ go install example/user/hello

$ go install .

$ go install

接下来让我们运行程序,确保它正常工作。为了更加简便地运行二进制文件,我们将把安装目录加入 PATH 环境变量。以下仅演示 Linux 系统的操作过程,Windows 用户参见此网页

$ 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 代码仓库提供了这样的元数据,所以让你的模块能被其他人使用的最简单的方法是让其 模块路径代码仓库的 url 一致。

从你的模块导入包

让我们编写一个 morestrings 包,并从 hello 程序中使用它。首先,为这个包创建一个目录,命令为 $HOME/hello/morestrings,然后在这个目录中创建一个名为 reverse.go 的文件,包含以下代码:

// 与标准string包提供的函数相比,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 函数以一个大写字母开头,所以它是可导出的(Exported),即可以在其他导入了 morestrings 包的包中使用。

让我们测试一下,使用 go build 命令编译。

$ cd $HOME/hello/morestrings
$ go build
$

这不会在当前目录生成可执行文件,而是将编译后的包保存在本地构建缓存中。

在确认 morestring 包构建完成后,让我们在 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
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 命令为导入的包添加缺失的模块需求(Module Requirements),并移除不再使用的模块需求。

$ 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 环境变量指向的目录的包/模块子目录。对于一个指定版本的模块,其下载内容会被所有依赖该版本的其他模块共享,因此 go 命令会将这些文件和目录标记为只读。要移除全部下载的模块,你可以使用 go clean -modcache 命令。

测试

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

你通过创建以 _test.go 结尾的文件编写测试,该文件中包含函数名以 Test 开头,函数签名为 func(t *testing.T) 的函数。测试框架会运行每一个这样的函数;如果一个函数调用了错误函数(Failure Function),比如 t.Error 或 t.Fail,则测试被视作失败。

为 morestring 包增加一个测试。创建文件 $HOME/hello/morestrings/reverse_test.go ,该文件包含以下代码:

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 包文档 以获取更多详情。

本文译自 https://go.dev/doc/code,有删改

未经许可,不得转载

posted @ 2022-10-19 08:56  名字被风吹走  阅读(234)  评论(0编辑  收藏  举报