Go入门:怎样编写Go代码
基本介绍
本文将描述如何开发一个简单的go包,并简单介绍go命令,以及获取、生成、安装go包和命令的标准方法。
代码结构
基本原则
- 将所有Go代码放在一个工作空间(workspace)下。
- 一个工作空间包含多个代码仓库。
- 每个仓库有一个或多个包(packages)。
- 每个包又包含一个或多个Go代码源文件。
- 包的路径在import中体现。
工作空间(workspace)
每个工作空间根目录下包括:
- src Go源代码
- src文件夹下放置多个版本控制的仓库(项目)。
- pkg 包对象
- bin 生成的可执行文件
go命令编译源文件,并生产对应的结果文件到pkg和bin目录。
一个典型的go工作空间是这样子的:
上边的例子包括两个仓库、项目(example和image)。其中项目example包括两个命令(hello和outyet)和一个二进制库(stringutil);项目image包含bmp包和其它。
一个典型的go工作空间包括多个代码库(由包和命令组成)。大多数程序员会把所有源代码和依赖放到一个工作空间。
各种不同的代码包编译生成命令和类库。这个我们以后单独讨论。
GOPATH环境变量
GOPATH环境变量又来标明你的工作空间的位置。大多数情况下也是开发Go代码唯一要设置的一个环境变量。
开始所有工作之前,你需要创建一个总的目录来当做工作空间,并且设置GOPATH变量指向这个目录。工作空间可以在任何目录下,在接下来的文档中我们使用home目录下的work目录
HOME/work来做例子。(一般也会直接把个人home目录
HOME设为工作空间)
注意:工作目录不能跟Go的安装目录一样。
$ mkdir $HOME/work
$ export GOPATH=$HOME/work
方便起见,也会直接把工作空间下的bin目录添加到PATH路径(这样就可以在任意目录下直接执行生成的Go命令)。
$ export PATH=$PATH:$GOPATH/bin
点击这里获取更多关于GOPAT环境变量的设置帮助。
包路径
包路径用来唯一标记一个包,包路径反映了包在本地工作空间或远程代码库(接下来会详细说明)中的位置。
标准库中的包直接使用短包路径,比如“fmp”或“net/http”。但是对于自己编写的包,基础路径的名字不能与标准库和其它一些外部库冲突。
如果你的代码直接保存在一些版本控制的代码库里,应该直接使用代码库的根目录作为基本路径。比如,如果你的代码都存放在github的user用户下(github.com/user),那github.com/user就应该是基本路径。
即使你现在还没有打算把代码发布带远程代码仓库,最好还是按照以后准备提交代码仓库的方式组织代码结构。实际中可以使用任何组合来标记路径名称,只要对应的路径名称不与基本库冲突。
我们接下来将使用githun.com/user作为基本路径。在工作空间下创建对应的目录来保存代码:
$ mkdir -p $GOPATH/src/github.com/user
第一个Go程序
为了编译和运行一个简单的Go程序,需要在工作空间下创建包路径(我们接下来将使用github.com/user/hello)和对应的包目录。
$ mkdir $GOPATH/src/github.com/user/hello
接下来,在目录下新建一个hello.go文件,包含以下内容:
package main
import "fmt"
func main() {
fmt.Printf("Hello, world.\n")
}
现在可以使用下边的go工具生成和安装这个程序:
$ go install github.com/user/hello
你可以在任何目录下执行上边的命令。go工具会根据GOPATH在对应工作空间下查找github.com/user/hello包。
你也可以忽略包路径而直接在对应代码包文件夹下执行go install命令:
$ cd $GOPATH/src/github.com/user/hello
$ go install
以上命令会生成hello命令,同时产生可执行二进制文件。然后将对应的二进制命令安装到工作空间的bin目录下。在我们的这个例子中,会直接安装到 GOPATH/bin/hello,也就是 HOME/work/bin/hello。
go命令执行成功时不会有任何输出,只有发生错误的时候才会打印对应的错误信息。
现在你可以在命令行中使用完整路径来执行这个程序:
$ $GOPATH/bin/hello
Hello, world.
或者,如果已经添加$GOPATH/bin到PATH,可以直接输入二进制名称来执行程序:
$ hello
Hello, world.
如果你正要使用版本控制系统,现在可以初始化代码仓库、添加文件,做第一次提交。再次强调,使用版本控制系统不是必须的。
$ 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程序使用。
跟可执行程序一样,第一步还是创建包路径和对应的包文件夹。
$ mkdir $GOPATH/src/github.com/user/stringutil
然后在包文件夹下新建reverse.go文件,内容如下:
// 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编译整个包:
$ go build github.com/user/stringutil
或者在包目录下直接执行go build:
$ go build
到这里还不会产生任何文件。你必须执行go insall命令生成输出库文件。
确认库文件已经产生后,就可以在之前的hello.go中使用这个库了:
package main
import (
"fmt"
"github.com/user/stringutil"
)
func main() {
fmt.Printf(stringutil.Reverse("!oG ,olleH"))
}
无论使用go安装包还是二进制文件,所有相关的依赖都会自动安装。所以当你安装hello程序时:
$ go install github.com/user/hello
对应的stringutil包也会自动被安装。
执行新的hello程序,你可以看到一个新的、已经被反转的消息:
$ hello
Hello, Go!
所有这些以后,你的工作空间应该是这样的:
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命令可以直接找到对应的包对象,避免不必要的重复编译。linux_amd64是为了根据操作系统和你的系统架构交叉编译。
所有Go可执行程序都通过静态方式链接在一起,所以在运行时是不需要相关的包对象(库)。
包命名
所有Go源代码都以下边的语句开始:
package name
其中name就是包引用是默认的名称。一个包中的所有文件必须使用同一个包名。
可执行命令的包名必须是main。
一个二进制文件下所有的包名不需要唯一,但是引用路径必须唯一。
了解更多关于Go的命名规范,可以参考实效Go编程。
测试
Go自带了一个轻量级的测试框架,由go test命令和testing包组成。
你可以通过新建xx_test.go写一个测试,其中包含若干个TestXXX函数。测试框架会自动执行这些函数;如果函数中包含tError或t.Fail, 对应的测试会被判为失败。
添加一个针对stringutil的测试文件$GOPATH/src/github.com/user/stringutil/reverse_test.go,包含以下内容:
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)
}
}
}
然后通过go test执行测试:
$ go test github.com/user/stringutil
ok github.com/user/stringutil 0.165s
同样,在包文件夹下可以忽略包路径而直接执行go test命令:
$ go test
ok github.com/user/stringutil 0.165s
远程包
包的引用路径用来描述如何通过版本控制系统获取包的源代码。go工具通过引用路径自动从远程代码仓库获取包文件。比如本文中用的例子也对应的保存在github.com/golang/example下。go可以通过包的代码仓库的url直接获取、生成、安装对应的包。
$ go get github.com/golang/example/hello
$ $GOPATH/bin/hello
Hello, Go examples!
如果工作空间中不存在对应的包,go会将对应的包放到GOPATH环境变量指明的工作空间下。(如果包已经存在,go跳过代码拉去而直接执行go install)。
执行上边的go get命令后,工作空间文件夹下是这样的结果:
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上的hello命令依赖于同个仓库下的stringutil库。hello.go使用同样的路径进行导入,所以go get命令能够直接找到并安装对应的依赖包。
import "github.com/golang/example/stringutil"
这也是你共享Go包的最好方式。
关于go工具远程包的更多信息,可以参考Go - 导入路径帮助文档。
接下来的事情
你可以订阅golang-announce邮件列表收取Go新版本的发布信息通知。
阅读高效Go编程来学习如何编写清晰、优雅的Go代码。
到这里学习Go编程语言。
访问Go文档深入了解Go以及基本库和工具。
到这里就结束了,参考原文How to Write Go Code.