如何开发GO代码

本文翻译自:https://golang.org/doc/code.html

简介

本文档演示了如何开发一个简单的Go包并介绍了Go自带的一些工具,包括标准的获取,构建和安装Go包的命令。

GO自带的工具要求你以特定方式组织代码。 请仔细阅读本文档。 它解释了入门GO开发最简单的方法。

代码组织

概述

  • GO程序员通常将它们所有的GO代码放在一个独立的工作区中。
  • 工作区包含许多版本控制存储库(例如由Git管理)。
  • 每个仓库包含一个或多个包。
  • 每个包由位于同一个文件夹中的一个或多个GO源代码文件组成
  • 包目录的路径决定其导入路径。

请注意,这不同于其他编程环境,其中每个项目都有一个单独的工作区,而工作区与版本控制库紧密相连。

工作区

工作区是一个有层级的目录,它包含三个子目录

  • src 包含GO源代码文件的目录
  • pkg 包含包对象(编译后的包文件)
  • bin 包含可执行的命令(生成的可执行文件)

go工具编译源码包并将生成的结果二进制文件安装到pkg目录和bin目录

src目录的子目录通常包含多个版本控制库,以此来跟踪一个或多个源码包的开发

下面是一个工作区的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bin/
hello # command executable
outyet # command executable
pkg/
linux_amd64/
github.com/golang/example/
stringutil.a # package object
src/
github.com/golang/example/
.git/ # Git repository metadata
truehello/
true hello.go # command source
trueoutyet/
true main.go # command source
true main_test.go # test source
truestringutil/
true reverse.go # package source
true reverse_test.go # test source
golang.org/x/image/
.git/ # Git repository metadata
truebmp/
true reader.go # package source
true writer.go # package source
... (many more repositories and packages omitted) ...

上面的树结构显示了一个包含两个版本库的工作区(example和image)。 example版本库包含两个命令(hello和outyet)和一个库(stringutil)。 image版本库包含bmp包和其他几个包。

一个典型的工作区包含许多个包含多个包和命令的源代码库。 大多数Go程序员将所有Go源代码和依赖关系保存在一个工作空间中。

命令和库是从不同类型的源包构建的。 我们稍后将讨论这种区别。

环境变量 GOPATH

环境变量GOPATH声明了你的工作区位置。这好像也是你开发go代码唯一需要配置的环境变量。

在开始开发之前,先创建一个工作空间目录并设置对应的环境变量GOPATH。你的工作空间可以在任何你喜欢的目录,但在该文档中我们使用$HOME/work。 需要注意的是该环境变量不是你安装GO开发环境时需要设置的环境变量GOROOT

1
2
$ mkdir $HOME/work
$ export GOPATH=$HOME/work

为了方便起见,将工作空间下的bin目录添加到你的PATH环境变量中。

1
export PATH=$PATH:$GOPATH/bin

想要学习更多关于如何设置GOPATH环境变量的知识,可以参考https://golang.org/cmd/go/#hdr-GOPATH_environment_variable

import路径

import 路径是一个包唯一标识的字符串。一个包的import路径相对于它所在的工作空间中的位置或一个远程的仓库(下面解释)

标准库中的包都是通过短路径给出的, 例如fmtnet/http。对我们自己开发的包来说,你必须选择一个基准路径来和标准包或其它第三方包作区分。

如果你将代码保存在某个代码库中,那么你应该使用该代码库的根目录作为基准路径。 例如,如果你在github.com/user上有一个GitHub帐户,那么它应该是您的基准路径。

请注意,在构建之前,您不需要将代码发布到远程代码仓库。 这只是一个组织你代码的好习惯,如果有一天你会发布它。 在实践中,你可以选择任何任意的路径名称,只要它能保持在GO标准库和更大的Go生态系统的唯一性即可。

我们将使用github.com/user作为我们的基准路径。在你的工作空间建立一个目录来跟踪源代码。

1
mkdir -p $GOPATH/src/github.com/user

第一个程序

为了编译和运行示例程序,首先需要选择一个包路径,(我们选择了 github.com/user/hello) 并在工作空间建立一个对应的包文件夹。

1
$ mkdir $GOPATH/src/github.com/user/hello

下一步,创建一个名为hello.go的源文件,该文件包含下面的内容。

1
2
3
4
5
package main
import "fmt"
func main() {
truefmt.Printf("Hello, world.\n")
}

现在你可以通过go工具来编译和安装该程序:

1
go install github.com/user/hello

go工具会在工作空间的src目录下查找包github.com/user/hello的源代码

需要注意的是你可以在你系统的任何位置来运行该命令。go工具会基于环境变量GOPATH指定的路径来查找包github.com/user/hello下的源码。

如果你在包目录中运行go install,则可以省略包路径:

1
2
$ cd $GOPATH/src/github.com/user/hello
$ go install

此命令构建结果是生成可执行二进制文件hello。 然后将该二进制文件(或在Windows下的hello.exe)安装到工作空间的bin目录。 在我们的例子中,这将是$GOPATH/bin/hello

go工具只在出现错误时打印错误输出,因此如果这些命令没有产生输出,则表示它们已成功执行。

现在可以通过在命令行中键入其完整路径来运行程序:

1
2
$ $GOPATH/bin/hello
Hello, world.

如过你将$GOPATH/bin添加到PAHT环境变量中,你也可以通过下面的命令来运行该程序。

1
2
$ hello
Hello, world.

如果你正在使用一个版本管理系统,那现在应该是初始化该版本库的好时机。添加文件并提交你的第一次变更。 如果你没有版本控制系统则下面的步骤不是必须的:

1
2
3
4
5
6
7
$ cd $GOPATH/src/github.com/user/hello
$ git init
Initialized empty Git repository in /home/user/work/src/github.com/user/hello/.git/
$ git add hello.go
$ git commit -m "initial commit"
[master (root-commit) 0b4507d] initial commit
1 file changed, 1 insertion(+) create mode 100644 hello.go

推送代码到远程仓库作为给读者的练习。

第一个库

让我们编写一个库并在hello中使用该库。

同样的,首先你需要选择一个包路径(我们选择了github.com/user/stringutil) 并在工作空间创建对应的目录。

$ mkdir $GOPATH/src/github.com/user/stringutil

接下来在该目录创建一个名为reverse.go的源文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
// 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 {
truer := []rune(s)
truefor i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
truetruer[i], r[j] = r[j], r[i]
true}
truereturn string(r)
}

现在通过命令go build来测试编译该包:

go build github.com/user/stringutil

如果已经进入包的源代码目录你可以直接执行下面的命令:

go build   

该命令不会有任何输出文件。如果需要生成文件你需要使用命令go install,该命令会把生成的文件放入pkg目录

在确认包stringutil能成功编译后,修改文件hello.go来使用该包:

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

import (
true"fmt"
true"github.com/user/stringutil"
)

func main() {
truefmt.Printf(stringutil.Reverse("!oG ,olleH"))
}

当go工具安装包或二进制可执行文件时会同时将依赖的包也进行安装。因此当你安装hello程序时:

$ go install github.com/user/hello

stringutil也会被自动安装。

执行新版本程序,你会看到一个新的反转的信息:

$ hello
Hello, Go!

在执行了上面的步骤后你的工作空间可能看起来是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
bin/
hello # command executable
pkg/
linux_amd64/ # this will reflect your OS and architecture
github.com/user/
stringutil.a # package object
src/
github.com/user/
hello/
hello.go # command source
stringutil/
reverse.go # package source

注意,go installstringutil.a对象放在pkg/ linux_amd64内的目录中,该目录反映其源目录。 这样在以后调用go工具时就可以找到包对象,并避免重新编译包。 linux_amd64部分有助于交叉编译,并且将反映系统的操作系统和体系结构。

包名

go源代码文件的第一行代码必须是:

package name

where name is the package’s default name for imports. (All files in a package must use the same name.)

Go’s convention is that the package name is the last element of the import path: the package imported as “crypto/rot13” should be named rot13.

Executable commands must always use package main.

There is no requirement that package names be unique across all packages linked into a single binary, only that the import paths (their full file names) be unique.

See Effective Go to learn more about Go’s naming conventions.

测试

Go has a lightweight test framework composed of the go test command and the testing package.

You write a test by creating a file with a name ending in _test.go that contains functions named TestXXX with signature func (t *testing.T). The test framework runs each such function; if the function calls a failure function such as t.Error or t.Fail, the test is considered to have failed.

Add a test to the stringutil package by creating the file $GOPATH/src/github.com/user/stringutil/reverse_test.go containing the following Go code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package stringutil

import "testing"

func TestReverse(t *testing.T) {
truecases := []struct {
truetruein, want string
true}{
truetrue{"Hello, world", "dlrow ,olleH"},
truetrue{"Hello, 世界", "界世 ,olleH"},
truetrue{"", ""},
true}
truefor _, c := range cases {
truetruegot := Reverse(c.in)
truetrueif got != c.want {
truetruetruet.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want)
truetrue}
true}
}

Then run the test with go test:

$ go test github.com/user/stringutil
ok      github.com/user/stringutil 0.165s

As always, if you are running the go tool from the package directory, you can omit the package path:

$ go test
ok      github.com/user/stringutil 0.165s

Run go help test and see the testing package documentation for more detail.

Remote packages

An import path can describe how to obtain the package source code using a revision control system such as Git or Mercurial. The go tool uses this property to automatically fetch packages from remote repositories. For instance, the examples described in this document are also kept in a Git repository hosted at GitHub github.com/golang/example. If you include the repository URL in the package’s import path, go get will fetch, build, and install it automatically:

1
2
3
$ go get github.com/golang/example/hello
$ $GOPATH/bin/hello
Hello, Go examples!

If the specified package is not present in a workspace, go get will place it inside the first workspace specified by GOPATH. (If the package does already exist, go get skips the remote fetch and behaves the same as go install.)

After issuing the above go get command, the workspace directory tree should now look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bin/
hello # command executable
pkg/
linux_amd64/
github.com/golang/example/
stringutil.a # package object
github.com/user/
stringutil.a # package object
src/
github.com/golang/example/
true.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

The hello command hosted at GitHub depends on the stringutil package within the same repository. The imports in hello.go file use the same import path convention, so the go get command is able to locate and install the dependent package, too.

import "github.com/golang/example/stringutil"            

This convention is the easiest way to make your Go packages available for others to use. The Go Wiki and godoc.org provide lists of external Go projects.

For more information on using remote repositories with the go tool, see go help importpath.

What’s next

Subscribe to the golang-announce mailing list to be notified when a new stable version of Go is released.

See Effective Go for tips on writing clear, idiomatic Go code.

Take A Tour of Go to learn the language proper.

Visit the documentation page for a set of in-depth articles about the Go language and its libraries and tools.

Getting help

For real-time help, ask the helpful gophers in #go-nuts on the Freenode IRC server.

The official mailing list for discussion of the Go language is Go Nuts.

Report bugs using the Go issue tracker.

文章目录
  1. 1. 简介
  2. 2. 代码组织
    1. 2.1. 概述
    2. 2.2. 工作区
    3. 2.3. 环境变量 GOPATH
    4. 2.4. import路径
    5. 2.5. 第一个程序
    6. 2.6. 第一个库
    7. 2.7. 包名
  3. 3. 测试
  4. 4. Remote packages
  5. 5. What’s next
  6. 6. Getting help
|