本文会演示简单的Go软件包的开发过程,并介绍了go
命令行工具,这是我们获取,构建和安装Go软件包和命令的标准方法。
go工具要求你以特定方式组织代码。我们会介绍Go安装启动和运行的最简单方法,一定要仔细阅读啊。
组织代码结构
概要
- Go 程序员一般会将他们的源代码存放在一个工作区中(多个项目放在一个工作区)
- 工作区中包含许多由 git 管理的代码仓库(也可以是其他版本控制工具管理的)
- 每个代码仓库包含一个或者多个 Go package
- 每个 package 由单一目录下的一个或多个Go 源码文件组成
- package 的目录路径决定了其导入路径
与其他编程语言不同的是,在其他编程语言里每个项目都有自己的工作区,并且工作区都与版本控制系统紧密相关。
工作区
工作区是一个目录层级,这个目录层级在顶层有两个目录:
-
src
目录,存放源代码文件。 -
bin
目录,存放可执行二进制文件。
go
命令工具会把src
中的Go 文件构建生成二进制文件放在bin
目录中。
src
子目录通常包含用 git 管理的多个代码仓库,他们对应一个或多个Go 包的开发源码。
一个典型的工作区中会包含多个源码仓库,对应多个可执行命令源码和包源码。大多数 Go 程序员会把他们的Go 源码和所有依赖的包都放在单一的工作区中。
下面的例子可以让你更好的了解Go 的工作区大概的样子:
bin/
hello # 可执行命令文件
outyet # 可执行命令文件
src/
github.com/golang/example/
.git/
hello/
hello.go # 命令文件源码
outyet/
main.go # 命令文件源码
main_test.go # 测试文件
stringutil/
reverse.go # package源码
reverse_test.go # 测试文件
golang.org/x/image/
.git/
bmp/
reader.go # package 源码
writer.go # package 源码
......
上面的目录树展示了工作区中的两个代码仓库(example 和 image)。example 仓库中包含两个命令hello 和 outyet(hello 和 outyet 目录中存放的就是两个命令的源码)一个被用作库的 package - stirngutil
。image仓库中包含一个bmp
包。
注意:不能使用符号链接(软链 ln -s)将文件链接到工作区中。
执行命令和库是从不同类的源码包构建出来的,这个之后的部分会进行说明。
GOPATH 环境变量
GOPATH
环境变量指定工作区的位置。它缺省为用户目录中名为go的目录,因此在Linux上为$HOME/go
,在Windows上通常为C:\Users\YourName\Go
。
如果想在其他位置放置工作区,则需要将GOPATH
设置为该目录的路径。请注意,GOPATH不得与GO安装路径相同。
命令go env GOPATH
打印当前有效的GOPATH
;如果环境变量未设置,它将打印默认位置。为方便起见,可以请将工作区的bin
子目录添加到系统环境变量$PATH
中
$ export PATH=$PATH:$(go env GOPATH)/bin
同时也把GOPATH
设置成系统的环境变量:
$ export GOPATH=$(go env GOPATH)
包的导入路径
一个导入路径是用来唯一标识包的字符串,包的导入路径和他在工作区中的位置相对应。标准库中的包具有较短的导入路径,如“fmt”和“net/http”。对于您自己的软件包,你必须选择一个不太可能与将来添加到标准库或其他外部库中的内容冲突的基本路径。
如果你将代码保存在某个源代码库中,那么应该使用该源代码库的根目录作为你的基本路径。例如,如果你在github.com上有一个GitHub帐户user,你创建的仓库都会以 github.com/user 为前缀,那么github.com/user
这应该是你的基本路径。
请注意,在构建代码之前,你不需要将代码发布到远程存储库。就像有一天会发布代码一样来组织代码,这是一个好习惯。实际上,您可以选择任意路径名,只要它是唯一的。
我们将使用github.com/user
作为基本路径。在工作区内创建一个保存源代码的目录:
$ mkdir -p $GOPATH/src/github.com/user
你的第一个Go程序
要编译并运行一个简单的程序,首先选择一个软件包路径(我们将使用github.com/user/hello),并在您的工作区内创建一个相应的软件包目录:
$ mkdir $GOPATH/src/github.com/user/hello
接下来,在该目录中创建一个名为hello.go的文件,添加以下代码:
package main
import "fmt"
func main() {
fmt.Println("Hello, world.")
}
现在,你可以使用go工具构建和安装该程序了:
$ go install github.com/user/hello
你可以从系统上的任何位置运行此命令。go命令工具通过在GOPATH
指定的工作区内查找github.com/user/hello
包来查找源代码。如果从软件包目录运行go Install
,可以省略软件包路径:
$ cd $GOPATH/src/github.com/user/hello
$ go install
go install
构建hello命令,生成一个可执行的二进制文件。然后,它将该二进制文件作为hello(在Windows下为hello.exe)安装到工作区的bin目录中,hello 可执行命令的位置为 $GOPATH/bin/hello
。
Go工具仅在发生错误时打印输出,因此如果这些命令没有产生输出,则代表它们已成功执行。
现在,你可以通过在命令行中键入程序的完整路径来运行该程序:
$ $GOPATH/bin/hello
Hello, world.
由于您已将$GOPATH/bin
添加到路径中,因此只需键入二进制文件的名字:
$ hello
Hello, world.
你的第一个 library
让我们编写一个库并在上面写的hello程序中使用它。
同样,第一步是选择软件包路径(我们将使用github.com/user/stringutil)并创建软件包目录:
$ mkdir $GOPATH/src/github.com/user/stringutil
接下来在目录中创建reverse.go
文件并添加如下代码:
// stringutil包 存放关于字符串的工具函数
package stringutil
// Reverse 将参数中的字符串反转后的字符串
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
不会产生输出文件。相反,它将编译后的包保存在本地构建缓存中。
在确认stringutil
包构建可以正确之后,修改原始的hello.go
(位于$GOPATH/src/github.com/user/hello中)以使用它:
package main
import (
"fmt"
"github.com/user/stringutil"
)
func main() {
fmt.Println(stringutil.Reverse("!oG ,olleH"))
}
再次编译安装 hello 程序后运行他,可以看到输出中的字符串已经被反转了。
$ hello
Hello, Go!
经过上面几步后你的工作区现在应该看起来像下面这样:
bin/
hello
src/
github.com/user/
hello/
hello.go
stringutil/
reverse.go
包名
go 源码文件中的第一行语句必须是:
package name
其中,name是用于导入的包的默认名称。(包中的所有文件必须使用相同的名称)
go的惯例是包名是导入路径的最后一个元素:作为“crypto/rot13”导入的包它的包名为rot13
。
生成可执行命令的源码文件必须以 main
作为包名。
go 中不要求链接到单个二进制文件的所有包的包名都是唯一的,只要求导入路径(它们的完整文件名)是唯一的。
测试
go有一个由go测试命令和测试包组成的轻量级测试框架。你可以通过创建一个名字以_test.go
结尾的文件来编写测试,该文件包含名为TestXXX的函数,签名函数为func(t*testing.T)
。测试框架运行每个这样的函数;如果函数调用失败函数,如t.Error或t.Fail,则认为测试失败。
通过创建包含以下go代码的文件$GOPATH/src/github.com/user/stringutil/reverse_test.go
,将测试添加到strangutil
包。
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
导入路径可以描述如何从版本控制系统(如Git)获取包源代码。Go工具使用此属性自动从远程仓库中获取包。例如,本文档中描述的示例也保存在GitHub 以github.com/golang/example托管的Git存储库中。如果将代码仓库的URL包含在软件包的导入路径中,go将会使用go get`自动获取、构建和安装它:
$ go get github.com/golang/example/hello
$ $GOPATH/bin/hello
Hello, Go examples!
如果工作区中没有指定的包,go get
将把它放在$GOPATH
指定的工作区中。(如果软件包已经存在,go get
将跳过远程获取,其行为变得与go install
相同。)。
发出上述go get
命令后,工作区目录树现在应该如下所示:
bin/
hello
src/
github.com/golang/example/
.git/
hello/
hello.go
stringutil/
reverse.go
reverse_test.go
github.com/user/
hello/
hello.go
stringutil/
reverse.go
reverse_test.go
托管在GitHub上的hello命令依赖于同一仓库中的stringutil
包。hello.go
文件中的导入使用相同的导入路径约定,因此go get
命令也能够定位和安装依赖包。
import "github.com/golang/example/stringutil"
What's Next
- 《Go语言之旅》https://tour.go-zh.org/list 了解 Go 语言的基础语法
- 《Go 入门指南》https://learnku.com/docs/the-... 通过了200 多个完整的代码示例和书中的解释说明来对所有涉及到的概念和技巧进行彻底的讲解。
- 《Go语言程序设计》https://yar999.gitbooks.io/go... 通过学习这本书会对 Go 有更全面的认识,强化自己的Go语言底层基础知识。
- 《Effective Go 中文版》https://learnku.com/docs/effe... 提供编写清晰高效、地道的 Go 代码的技巧。