如何编写Go代码

目录 [−]

  1. 代码组织
    1. 工作区Workspace
    2. GOPATH 环境变量
    3. Package路径
    4. 第一个 Go 程序
    5. 第一个Go库
    6. Package name
  2. 测试
  3. 远程 package
  4. 下一步
  5. 寻求帮助

官方原文: How to Write Go Code,
根据最新官方文档翻译。翻译参考wang_yb如何写 go 代码
因为官方文档有更新,我根据新版本的文档重新进行了翻译。

本文演示如何开发一个简单的 go package, 以及 go tool 的使用方法,
即获取(fetch), 编译(build), 安装(install) go package 的标准方法和命令.

go tool 需要你按照一定的标准来组织代码. 请仔细阅读本文.
它介绍了用来构建和运行 Go 程序的最简单方法.

介绍本文的视频参照: https://www.youtube.com/watch?v=XCsL89YtqCs。

代码组织

工作区Workspace

go tool 是设计用来和公共仓库的开源代码一起工作的.
即使你不需要发布你的代码, go tool 的工作模型也同样适用于你.

Go 代码必须保存在 工作区 中, 工作区 就是一个特定的目录结构, 根目录下有如下3个目录:

  • src 目录: 存放 go 源码文件, 按 package 来组织 (一个 package 一个文件夹)
  • pkg 目录: 存放 package 对象
  • bin 目录: 存放可执行的命令command

go tool 编译 src 下的文件, 并将编译好的二进制文件分别放入 pkg 或者 bin 文件夹中.
src 目录可以包含多个 版本控制仓库(比如 Git 或 Mercurial), 用来管理代码的开发.

一个实际的工作区的目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
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

这个工作区包含一个仓库(example),由两个命令组成(hellooutyet),以及一个库(stringutil)。

一个典型的工作区可能包含很多源码库,这些源码库包含着N多的packagecommand。大部分的Go程序员会将他们的源码和依赖放在一个单一的工作区中。

命令Command和库libraries由各种source package编译而来。我们稍后再介绍

GOPATH 环境变量

GOPATH 环境变量指向你的 工作区 的位置. 这是你开发 Go 代码时唯一需要设置的环境变量.

开始开发时, 创建 工作区 的文件夹, 并设置对应的 GOPATH 环境变量.
你的 工作区 可以是任意文件夹, 本文中使用的路径是 $HOME/work
注意 不要把 GOPATH 设置为 go 的安装路径. (另一种常用设置方式是设置GOPATH=$HOME)

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

为了方便编译出的命令的执行, 将上面的 bin 目录加入到 PATH:

1
$ export PATH=$PATH:$GOPATH/bin

想了解更多设置GOPATH 环境变量的知识,可以查看go help gopath

Package路径

标准库中的 package 只要使用短路径即可, 比如 "fmt", "net/http"。
对于自己的 package, 必须选一个基本路径以防止以后和标准库, 或者其他第三方的库产生冲突。
如果你的代码保存在某个代码仓库, 那么就可以使用那个代码仓库的根目录作为你的 package 的基本路径。
比如, 你有个 github 的账户在 github.com/user, 就可以使用 github.com/user 作为你的基本路径。

注意 在能够正确编译代码之前, 你并不需要发布你的代码到远程的代码仓库。
但是如果有一天你发布代码的话, 组织好你的代码是个好习惯。
实际上, 你可以使用任意的路径名称, 只要它在 go 标准库和庞大的 go 生态系统中是唯一的。
我们使用 "github.com/user" 作为基本路径, 然后在工作区中创建文件夹来保存代码:

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

第一个 Go 程序

为了编译和运行一个简单的 GO 程序, 首先要确定 package 路径(这里使用 github.com/user/hello),
并且在工作区中创建对应 package 文件夹.

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

下一步, 在上面文件夹中创建 hello.go 文件, 文件内容如下:

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

然后, 可以通过 go tool 来编译和安装上面的 hello 程序。

1
$ go install github.com/user/hello

注意 你可以在任何路径下运行上述命令, go tool 会根据 GOPATH 环境变量来从工作区中查找 github.com/user/hello。
如果在 package 所在文件夹中运行 go install, 也可以省略 package 路径。

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

上面的命令编译了 hello 命令, 并产生此命令的二进制可执行文件。
然后将二进制文件 hello 安装到了 工作区 的 bin 文件夹下(Windows 下是 hello.exe)
在我们的例子中, 就是 $GOPATH/bin/hello, 即 $HOME/work/bin/hello。

go tool 只有在出错时才会输出信息, 如果上面的 go 命令没有输出就说明执行成功了.
然后, 就可以在命令行中运行这个命令了.

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

或者, 如果你将 $GOPATH/bin 加入到 PATH 中了的话, 也可以执行执行 hello 命令。

1
$ hello

如果你使用了代码版本管理工具, 正好可以初始化你的仓库, 添加文件, 并 commit 你的第一个改变。
当然这个步骤是可选的, 写 go 代码并不强制要求使用代码版本管理工具。

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

你可以把发布这个仓库作为练习题。

第一个Go库

让我们来写一个库, 并将之用于上面的 hello 程序中.
同样, 首先确定 package 路径 (这里使用 github.com/user/stringutil), 并创建对应的文件夹。

1
$ 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 {
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 来编译此 package:

1
$ go build github.com/user/stringutil

或者在 package 的目录下, 直接运行 go build

1
$ go build

上面的命令不会产生输出文件。 为了生成输出文件, 必须使用 go install 命令, 它会在 pkg 文件夹下生成 package 对象.
stringutil package 编译成功之后, 修改之前的 hello.go 文件:

1
2
3
4
5
6
7
8
9
10
11
package main
import (
"fmt"
"github.com/user/stringutil"
)
func main() {
fmt.Printf(stringutil.Reverse("!oG ,olleH"))
}

无论用 go tool 安装 package 对象还是 二进制文件, 它都会安装所有的依赖.
所以当你安装 hello 程序时,

1
$ go install github.com/user/hello

stringutil package 也会被自动安装.

运行新的 hello 程序, 可以看到如下输出:

1
2
$ 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 install 将 stringutil.a 放进了 pkg/linux_amd64 文件夹下与源码对应的目录中。
以后, go tool 就可以找到这个 package, 从而判断是否需要重新编译。
linux_amd64 是表示当前使用的系统, 它的目的是为了区分交叉编译出的其他平台的 package。

Go 编译出的二进制文件都是静态链接的, 所以上面的 bin/hello 在执行时并不需要 linux_amd64/go-files/stringutil.a 文件。

Package name

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

1
package name

这里的 name 作为 package 的默认名称, 让其他 package import 的时候用.(同个 package 中的所有文件必须使用相同的 name)。
Go 约定 package name 是 import path 中最后一部分。
也就是说, 如果一个 package 被引用时写成 "crypto/rot13", 那么这个 package 的 name 就是 rot13。

编译为可执行文件的代码的 package name 必须是 main。

连接进同一个二进制文件的package name 不一定要唯一, 只要 pakage 的 import path 是唯一的就行。
也就是上面的 crypto/rot13 必须唯一, 但是可以有 another-crypto/rot13。

Go 的命名规则可以参考: [Effective Go](http://golang.org/doc/effective_go.html#names)

测试

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

测试文件的名称以 _test.go 结尾, 其中包含格式如 func TestXXXX(t *testing.T)的函数。
测试框架会执行每个这样的函数, 如果函数中调用了 t.Error 或者 t.Fail, 就认为测试失败。

给上面的 package stringutil 增加测试文件, 路径: $GOPATH/src/github.com/user/stringutil/reverse_test.go , 内容如下:

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) {
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)
}
}
}

运行测试的方法如下:

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

或者进入到 package stringutil 的目录中后, 直接运行:

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

通过 go help test 或者 http://golang.org/pkg/testing/ 来进一步 GO 的测试框架。

远程 package

Go 的 import path 指示如何从版本管理系统(Git 或者 Mercurial) 中获取 package 的源码。go tool 可以利用这个特性来自动获取远程仓库的代码。
比如, 下面的例子中使用的代码同时也保存在 github 上(http://github.com/golang/example)。
如果你在代码中 import 了上面这个远程的 package, 那么 go get 命令会自动 获取, 编译, 安装这个 package。

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

如果工作区中没有指定的 package,go get 命令会把这个 package 下载到 GOPATH 中定义的第一个工作区中。
(如果这个package已经存在,go get会跳过获取这一步,执行go install相同的行为)

上面的 go get 命令执行之后, 文件夹结构大致如下:

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/
.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.com 上的 hello 程序依赖同一个仓库中的 package stringutil,
即 github.com 上的 hello.go 中引用了 github.com 上的 package stringutil, 所以, go get 命令也下载, 编译, 安装了 stringutil 模块。

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

这个特性可以让你的 go package 很容易的被别人使用.
Go Wikigodoc.org 上列出了很多第三方 Go 工程。
关于使用 go tool 来使用远程仓库的更多信息, 请参考: go help importpath

下一步

  • 订阅 golang-announce 邮件列表来了解最新的 Go release 信息
  • Effective Go 作为参考资料来编写整洁, 地道的 Go 代码
  • 通过 A Tour of Go 来完成一次 go 的旅行
  • 访问 documentation page 来了解一系列关于Go语言的有深度的文章, 以及 Go 库和工具.

寻求帮助