怎样编写Go程序
简介
本文演示一个简单的Go包开发过程,并介绍`go tool—-获取,build,安装Go包的标准方式。
go tool
要求以特定的方式组织代码,请仔细阅读本文,以便以最简单的方式运行安装你的代码。
代码组织
overview
- 一般将所有代码保存在单个
workspace
- 单个workspace包含多个
git
仓库 - 每个git仓库包含一个或多个
package
- 每个package包含一个或多个源文件
- pacage的目录决定其
import path
注意这和其它每个工程有单独的workspace、每个workspace都与git仓库关联的编程环境是不太相同的。
workspace
worksapce
一个包含以下三个目录的目录结构。
- src 源文件
- pk 包目标文件
- bin 可执行文件
go tool
build源文package包到pkg目录,install目标二进制文件到Bin目录。
典型的src目录下包含多个git仓库,以跟踪每个package的开发。
以下例子是一个包含两个package的workspace :1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24bin/
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
golang.org/x/image/
.git/ # Git repository metadata
bmp/
reader.go # package source
writer.go # package source
... (many more repositories and packages omitted) ...
一个典型的工作空间包含很多源文件仓库,每个仓库有多个package和command。大多数Go程序员将所有源代码与依赖保存在单个workspace下。
command和library从不同的package中build来,我们将稍后讨论。
GOPATH
GOPATH指示工作空间,它可能是你唯一需要指定的环境变量。
接下来,我们将创建工作空间并设置对应的GOPATH。它可以是任意目录,我们使用$HOME/work为例,注意它不能与Go安装目录相同。
1 | $ mkdir $HOME/work |
方便起见,将工作空间的bin目录加入path1
$ export PATH=$PATH:$GOPATH/bin
更多内容参见go help gopath
import path
import path 是一个唯一标识package的字符串。package的import path对应它在工作空间或者远程仓库中的位置。
标准库中的包有短Improt path如”fmt” 和 “net/http”, 自己的包则必须选择一个不太可能与将来可能添加的标准库或者外部库冲突的基础目录。
如果你将代码放置在某个源代码库下,那么你应该用那个源代码库的root作为你的基础目录。例如你有github帐号”github.com/user”,那么它应该作为你的基础目录。
编译Go程序并不要求你一定将代码发布到远程仓库,它只是希望你能以一种好的习惯来组织代码。实际上你可以任意选择path name,只要它不同于标准库或者大Go生态冲突。
我们使用github.com/work作为基础目录,在workspace中创建此目录以存储代码。1
$ mkdir -p $GOPATH/src/github.com/user
第一个程序
选择一个package名字并在基础目录下创建同名目录。1
$ mkdir $GOPATH/src/github.com/user/hello
在hello目录下创建一个hello.go文件,内容如下1
2
3
4
5
6
7package main
import "fmt"
func main() {
fmt.Printf("Hello, world.\n")
}
现在你可以用go tool编译和安装该程序1
$ go install github.com/user/hello
你可以在任意地方运行以上命令,go tool会在GOPATH指定的workspace下寻找github.com/user/hello包。
如果当前在package所在目录,那么package path可以省略1
2$ cd $GOPATH/src/github.com/user/hello
$ go install
以上命令编译生成可执行的hello二进制命令将其安装到工作空间下的bin目录,在本例中是 $HOME/work/bin/hello
。
go tool只在错误时输出信息,如果没有输出,则安装成功。
现在你可以直接输入hello
来运行命令。1
2$ hello
Hello, world.
如果你使用git,那么现在是建仓的好时候。当然,这也是可选的。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
第一个库
我们将编写一个库并在hello程序中使用。
同样第一步是选择package path并建立目录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)
}
测试编译package1
$ go build github.com/user/stringutil
这不会生成输出文件,如果需要,使用go install,它会在在pkg目录下生成package的目标文件。
确认编译通过后,修改hello.go1
2
3
4
5
6
7
8
9
10package main
import (
"fmt"
"github.com/user/stringutil"
)
func main() {
fmt.Printf(stringutil.Reverse("!oG ,olleH"))
}
注意import中的变化。
当go tool安装包或者二进制时,它也安装所有的依赖,所以当你1
$ go install github.com/user/hello
stringutil
包也会自动安装。
运行更新后的程序1
2$ hello
Hello, Go!
现在目录结构应该是这样的。1
2
3
4
5
6
7
8
9
10
11
12bin/
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将stirngutil
包安装到了pkg的子目录linux_amd64下的对应目录中,目标文件为stringutil.a
,这便于go tool接下来寻找,避免不必要的重复编译。linux_amd64
有助于cross-compilation,指示了系统OS和架构。
Go可执行文件是静态链接的,程序运行并不需要package目录文件。
package name
go程序的第一句代码必须是1
package name
name是包的默认import名,包中的所有文件使用相同的name. Go的惯例是name与目录名最后一级相同,例如import path是”crypto/rot13”的包,其package name为“rot13”.
可执行命令必须使用”package main”.
更多详细信息可参见Effective Go
Testing
Go有一个轻量级的测试框架,由go test 命令和testing包组成。
测试文件名以”_test.go”结尾,其中包含以”Testxxx”命名,签名为func (t *testing.T)的函数。test框架会运行每一个函数,如果函数调用了t.Error 或 t.Fail类似方法,则认为测试失败。
添加$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
19package 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所有目录运行,可忽略path1
2$ go test
ok github.com/user/stringutil 0.165s
更多信息可参见 go help test 和testing package documentation
remote package
通过import path就能得知如何使用git/Mercurial等工具获得源代码。本例已经放置在github.com/golang/example.通过go get可以自动获取、编译和安装代码。
1 | $ go get github.com/golang/example/hello |
如果指定的package不在工作区下,go get
会将获取并置于GOPATH指定的第一个工作空间。
以上命名惯例保证了代码便于被其它人使用go wiki 和 godoc.org提供外部Go项目的列表。
更多使用远程仓库的信息可参见go help importpath.