- 说明
- [官方原文](https://golang.org/doc/code.html)
- 本文按个人理解进行直译,水平有限,难免有错或偏差,欢迎批评指正
- 在Go的官方文档中,经常使用命令(command)这个词来指代可执行程序,如命令行应用程序,这个会让我们新手在阅读文档时感到困惑。所以,需要记住,在Go语言中,命令(command)是指任何可执行程序。作为对比,包(package)更常用来指语义上可导入的功能单元
- [Introduction](#1)
- [Code Organization](#2)
- [overview](#2.1)
- [workspace](#2.2)
- [the GOPATH environment variable](#2.3)
- [import paths](#2.4)
- [your first program](#2.5)
- [your first library](#2.6)
- [package names](#2.7)
- [Testing](#3)
- [remote packages](#4)
- [what`s next](#5)
- [getting help](#6)
<h2 id='1'> introduction </h2>
- 本文档主要演示一个简单 go 包的开发过程和对 go tool 的介绍,以及获取,构建和安装go包和命令的标准方式
- Go tool 要求使用指定的方法组织代码。
- 如果可以翻墙看YouTube的话,有个[演示视频](https://www.youtube.com/watch?v=XCsL89YtqCs)可以看
<h2 id='2'> Code Organization </h2>
<h3 id='2.1'> overview </h3>
- 通常来说Go开发者将他所有的源代码放到一个单个的工作空间(***workspace***)中
- 一个工作空间中包含多个由比如Git这样的版本控制系统的进行控制的仓库(或者说资料库)(***repositories***)
- 每个仓库包含一个或者多个包(***package***)
- 每个包在一个单独的目录中,该目录下包含一个或者多个GO源代码文件
- 到包的目录的路径决定了它的导入路径(***import path***)
需要注意,上面这个特点,与其他语言编程环境中每一个项目都放在一个分开的单独工作空间并且每个工作空间都几乎绑定到一个版本控制系统控制的仓库,是不同的,(比如 node 的项目是可以放在不同的目录中的,而不必将所有 node 的项目都放到一个工作空间的目录下)
<h3 id='2.2'> workspaces </h3>
一个工作空间是一个层次结构目录,在其根目录包含以下三个目录
- src 包含Go语言的源代码文件
- pkg 包含包对象
- bin 包含可执行程序
go tool 构建源代码并将生成的二进制文件放到 pkg 和 bin 目录中。
src 子目录通常包含多个由比如Git或者Mercurial这样的版本控制系统控制的仓库来追踪一个或者多个源包的开发过程。
下面是个实际中的关于工作空间的简要例子:
```go
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
golang.org/x/image/
.git/ # Git repository metadata
bmp/
reader.go # package source
writer.go # package source
... (many more repositories and packages omitted) ...
```
上面的结构树展示了一个工作空间包含两个仓库(example和image)。example库包含两个可执行程序(hello和outyet)和一个库(stringutil)。image仓库包含bmp包和几个其他包
一个典型的工作空间包含多个源代码仓库,每个仓库包含许多包和可执行程序。绝大多数的go开发者将他们的源代码和依赖都放到一个工作空间中。
可执行程序和库(***libraries***) 是从不同的源码包构建而来。
<h3 id='2.3'> the GOPATH environment variable </h3>
GOPATH 环境变量指明了工作空间的位置。它默认是在你的主目录中的一个名为go的目录, 所以在Unix下就是```$HOME/go```, 在Plan9下就是```$home/go```, 在Windows下就是```%USERPROFILE%/go```(通常是```C:\Users\YourName\go```).
如果想在一个不同的目录下工作,需要 [设置 GOPATH](https://github.com/golang/go/wiki/SettingGOPATH) 到想指定的目录.(另一个通常的设置是设置 ```GOPATH=$HOME```).<strong>需要注意的是 GOPATH 和 go的安装路径不能相同</strong>.
```go env GOPATH```命令打印出当前正在起效的GOPATH,如果GOPATH还没有被设置,那就打印出默认位置(即上面默认的GOPATH的值)
为了方便,添加工作空间的 bin 子目录到path:
```
$ export PATH=$PATH:$(go env GOPATH)/bin
```
为了简短一点,上面脚本在本文末使用 ```$GOPATH``` 替换 ```$(go env GOPATH)```.
要学习更多关于GOPATH环境变量相关的信息,查看 [go help gopath](https://golang.org/cmd/go/#hdr-GOPATH_environment_variable)
要使用通用工作空间位置,查看[设置GOPATH 环境变量](https://github.com/golang/go/wiki/SettingGOPATH)
<h3 id='2.4'> import paths </h3>
一个导入路径 ***import path*** 是唯一性地标志一个包的字符串。一个包的导入路径对应它在工作空间或者一个远程仓库中的位置(下面解释).
标准库(standard library)中的包通常都给出一个比如 "fmt"或者"net/http"这样简短的import path. 对于你自己的包,需要选一个不会跟未来被添加到标准库或者其他拓展库的命名冲突的基础路径(base path)。
我们选择 github.com/user 作为我们的基础路径。在工作空间中创建一个目录来存放源代码:
```
$ mkdir -p $GOPATH/src/github.com/user
```
<h3 id='2.5'> your first program </h3>
为了编译和运行一个简单的程序,首先选一个包路径(我们将使用 github.com/user/hello)然后在工作目录中创建一个对应的包目录
```
$mkdir $GOPATH/src/github.com/user/hello
```
然后,在那个目录里面创建一个命名为hello.go的文件,并把下面的代码放上去:
```go
package main
import "fmt"
func main(){
fmt.Printf("Hello, world.\n")
}
```
现在,可以使用go tool 来 构建(***build***) 并且安装上面的程序了:
```
$ go install github.com/user/hello
```
你可以在你系统的任何地方运行上面的命令. go tool 会根据 GOPATH 指明的工作空间到 github.com/user/hello 包中找到源代码.
如果从包目录中运行 go install,你可以忽略掉包的路径:
```
$cd $GOPATH/src/github.com/user/hello
$go install
```
上面的命令将会构建 hello 命令,产生一个可执行二进制文件。然后将二进制文件以hello命名(在 Windows下是 hello.exe)安装到工作空间的 bin 目录下。在我们的例子中就是 ```$GOPATH/bin/hello```, 也就是 ```$HOME/go/bin/hello```.
go tool 只有在产生错误时才打印输出,所以如果上面的命令执行之后没有产生任何打印输出,那就是执行成功了。
现在可以通过全路径运行命令:
```
$ $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
```
<h3 id='2.6'> your first library </h3>
选择一个包路径(在这我们将使用 github.com/user/stringutil)然后创建一个包目录来写一个库并使用:
```
$ mkdir $GOPATH/src/github.com/user/stringutil
```
接着在上面目录中创建一个名为 reverse.go 的文件并放上下面的内容:
```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 install,这样的话会在工作空间的 pkg 的目录中产生一个包对象。
确认 stringutil 包被构建之后,更改hello.go(在```$GOPATH/src/github.com/user/hello```目录下)来使用它
```go
package main
import (
"fmt"
"github.com/user/stringutil"
)
func main() {
fmt.Printf(stringutil.Reverse("!oG ,olleH"))
}
```
任何时候 go tool 安装了一个包或者二进制文件, 包或者二进制文件里面的所有依赖也会被安装,所以当安装 hello 程序, stringutil 包也同时会自动被安装
```
$go install github.com/user/hello
```
执行新版本的程序,应该可以看到一个新的信息
```
$ hello
Hello, Go!
```
上面所有的步骤执行完之后,工作空间看起来应该是下面这样的:
```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 tool 可以找到包对象并避免对包进行不必要的重编译。linux_amd64可以帮助交叉编译,并反映操作系统和系统的结构。
Go 命令可执行文件是静态链接的:运行 Go 程序不需要包对象的存在
<h3 id='2.7'> package names </h3>
Go 源文件的第一条语句必须是:
```go
package name
```
***name*** 是包的导入默认名字(其他文件导入这个包时使用的名称)(一个包中的所有文件都必须是相同的***name***)
包名作为导入路径的最后一个元素是Go的惯例。如果被导入的包是```cryto/rot13```那么包应该被命名为 ```rot13```。
可执行程序必须使用总是使用 package main。
被链接到一个单个二进制文件的所有包,这些包的名称不要求名字唯一性,只要包的导入路径(包的完整的文件名称)是唯一的即可
查看 [Effective Go](https://golang.org/doc/effective_go.html#names) 来了解更多关于包的命名惯例信息
<h2 id='3'> Testing </h2>
Go 有一个由 go test 命令和 testing 包组成的轻量级的测试框架.
创建一个以 _test.go为文件名后缀的文件,文件中包含名为 TestXXX 的签名 func (t *testing.T)的函数,通过这样的方式来编写测试。测试框架运行每一个这样的函数,如果函数调用类似 t.Error 或者 t.Fail 的报错函数,那么说明这里面对应的测试用例已经失败.
创建 ```$GOPATH/src/github.com/user/stringutil/reverse_test.go```文件,把下面的代码放上去,这样就为 stringutil包添加了测试.
```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 github.com/user/stringutil
ok github.com/user/stringutil 0.165s //这是运行命令的结果
```
另外,如果你是在包的目录下的话,不用跑上面的命令,直接跑下面的命令即可
```
$ go test
ok github.com/user/stringutil 0.165s //这是运行命令的结果
```
运行 [go helo test](https://golang.org/cmd/go/#hdr-Test_packages) 以及查看 [testing package documentation](https://golang.org/pkg/testing/)来了解更多信息
<h2 id='4'> remote packages </h2>
一个导入路径可以描述如何通过使用比如git或者marcurial这样的版本控制系统来获取包的源代码。go tool 使用这个特性来自动从远处仓库获取包。举个栗子,在这个文档中描述的例子同时是一个托管在 github.com/golang/example 的Git仓库。如果在包的导入路径中包含这个仓库的URL,那么, go get 会自动地获取,构建,然后安装它:
```
$ go get github/com/golang/example/hello
$ $GOPATH/bin/hello
Hello, Go examples?
```
如果指定的包没有出现在工作空间中,go get 将会把该指定的包放置通过GOPATH指定的第一个工作空间中。(如果该包早已经存在将要被放置的工作空间中,go get 将会跳过从远程仓库获取的过程从而继续执行go install)
执行完上面 go get 命令之后,工作空间的目录树看起来应该是下面这样的:
```go
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 也可以找到并安装依赖的包。
```go
import "github.com/golang/example/stringutil"
```
这个约定是让你的go 包可以被其他go开发者使用的最简单的方法。[Go wiki](https://github.com/golang/go/wiki/Projects)和[godoc.org](https://godoc.org/)提供里go的拓展项目.
想要了解更多的关于使用 go tool 来获取远程仓库的信息,查看[go help importpath](https://golang.org/cmd/go/#hdr-Remote_import_paths)
<h2 id='5'> what`s next </h2>
看一下 [Effective Go](https://golang.org/doc/effective_go.html)
看一下 [A Tour of Go](https://tour.golang.org/welcome/1)
看一下 [documentation page](https://golang.org/doc/#articles)
<h2 id='6'> getting help </h2>
如果需要实时帮助,访问[Freenode](http://freenode.net/)
官方关于go的探讨的邮件列表在[Go Nuts](https://groups.google.com/forum/#!forum/golang-nuts)
有bug想上报?到[Go issue tracker](https://github.com/golang/go/issues)
有疑问加站长微信联系(非本文作者))