15

简介

本文演示了一个简单的Go语言包的开发,以及 go tool 命令的使用,包含:获取、构建、安装Go包和命令的标准方法。

go tool 要求用特别的方式来组织你的Go代码。仔细阅读本文,它解释了启动和运行Go安装的最简单方法。

代码组织

概述

  • 程序员通常会将所有Go代码保存在一个工作区中
  • 工作空间包含许多版本控制的仓库(repo)(例如,由Git管理的)
  • 每个仓库包含一个或多个包
  • 每个包由一个目录中的一个或多个Go源文件组成
  • 包目录的路径确定其导入路径

请注意,这与其他编程环境不同,在这些环境中,每个项目都有一个单独的工作区,工作区与版本控制仓库紧密相关。

工作区

工作空间是一个目录层次结构,其根目录有两个目录:

  • src 用于存放Go原文件
  • bin 存放可执行的命令

go工具构建二进制文件并将其安装到 bin 目录。

src 子目录通常包含多个版本控制的仓库(例如Git),用于跟踪一个或多个源包的开发。

在实践中,工作区应该是什么样子呢? 下面给出一个例子:

bin/
    hello                          # command executable
    outyet                         # command executable
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) ...

上面的这个树型结构展示出一个工作区包含了2个仓库(exampleimage)。example 仓库包含了2个命令(hellooutyet)和一个库(stringutil)。image 仓库包含了 bmp 包和 一些其他的包。

通常工作区会包含很多的源仓库(包含需要多和命令)。大多数的Go开发者都会把他们的源代码和依赖存放在一个工作区。

命令和库是从不同类型的源包构建的。我们稍后会讨论这种区别。

GOPATH 环境变量

GOPATH 环境变量用来指定工作区的位置。默认是用户主目录的 go 目录,如在Linux和macOS上是 $HOME/go, 在Windows上是 C:\Users\YourName\go

go env GOPATH 命令会打印出当前有效的 GOPATH; 如果环境变量没有被设置会打印出默认的位置。

为方便起见,将工作空间的 bin 子目录添加到 PATH

$ export PATH=$PATH:$(go env GOPATH)/bin

为简洁起见,本文档其余部分中的脚本使用 $GOPATH 而不是 $(go env GOPATH)。即

$ export PATH=$PATH:$GOPATH/bin

而如果还没有设置 $GOPATH 就运行写好的脚本,你需要替换为 $HOME/go, 否则需要执行:

$ export GOPATH=$(go env GOPATH)

要学习更多关于gopath环境变量,可以使用查看帮助 go help gopath

要使用自定义的工作区,可以查看 https://golang.org/wiki/SettingGOPATH

导入路径

导入路径(import path)是唯一标识包的字符串。包的导入路径对应于其在工作空间内或远程仓库中的位置(如下所述)。

标准库中的包具有简短的导入路径,例如 fmtnet/http。对于我们自己开发的包您必须选择一个基本路径,该路径不太可能与将来添加到标准库或其他外部库中发生冲突。

如果将代码保存在某个源仓库中,则应使用该源仓库的根作为基本路径。例如,如果你在 github.com/user 上有一个GitHub帐户,那么这应该是你的基本路径。

请注意,在构建代码之前,无需将代码发布到远程仓库。组织代码只是一个好习惯,好像有一天你会发布它一样。实际上,你可以选择任意路径名称,只要它对标准库和更大的Go生态系统是唯一的。

我们将使用 github.com/user 作为我们的基本路径。在工作区内创建一个目录,用于保存源代码:

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

你的第一个Go程序

要编译和运行一个简单的程序,首先要选择一个包路径(我们会使用 github.com/user/hello),然后在工作区里创建一个相应的包目录:

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

接下来,在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

该命令会生成一个 hello 命令,生成一个可执行的二进制文件。同时安装到工作区目录下的 bin 目录,生成的可执行文件是 hello(如果是windows则是 hello.exe)。
在本例子中是 $GOPATH/bin/hello, 也就是 $HOME/go/bin/hello

当有错误发生的时候,go工具仅会打印除错误,所以如果没有任何输出的时候说明已经执行成功。

可以通过全路径来运行:

$ $GOPATH/bin/hello
Hello, world.

如果已经把 $GOPATH/bin 加入到了 PATH, 可以直接输入二进制文件名:

$ hello
Hello, world.

如果你正在使用一个版本控制系统,比如Git,现在是时候来初始化来生成一个仓库(repository),然后添加文件,做第一次提交。

当然这一步是可选的,不一定非要使用版本控制系统来写Go代码
$ 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
$ git push origin master  

你的第一个库

我们再来写一个库,并在 hello 程序中使用。

首先,第一步确定好包路径,我们使用 github.com/user/stringutil, 创建包目录

$ 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

如果已经在 github.com/user/stringutil 目录里,则直接执行:

$ go build

当然该命令不会生成文件,而是把编译好的包放到了本地的构建(build)缓存里。
确实 stringutil 包被编译后, 修改 hello.go:

vim $GOPATH/src/github.com/user/hello

修改后的:

package main

import (
    "fmt"

    "github.com/user/stringutil"
)

func main() {
    fmt.Println(stringutil.Reverse("!oG ,olleH"))
}

再次安装

$ go install github.com/user/hello

执行:

$ hello
Hello, Go!

通过上面的一些步骤后,现在我们的结构是这样子的:

bin/
    hello                 # command executable
src/
    github.com/user/
        hello/
            hello.go      # command source
        stringutil/
            reverse.go    # package source

包名

在Go原文件中第一个使用的语句必须是

package name

其中 name 就是包的默认名称。一个包中的所有文件必须使用相同的包名。

Go的约定是包名称是导入路径的最后一个元素,例如导入的包 crypto/rot13, 包名就是 rot13

如果是可执行的文件,包名必须使用 main

不强制要求所有的包名都是唯一的,但是要求导入的路径必须是唯一的(全路径文件名)。

更多关于go的命名规范可以查看 Effective Go

测试

Go提供了一个由 go testtesting 包组成的测试框架。

通过创建一个以 _test.go 结尾的文件,里面写有以 TestXXX 开头的函数。测试框架会运行每一个这样的函数,如果函数调用了一个失败的函数,如 t.Errort.Error, 那么测试就算不通过。

通过添加一个测试文件到 stringutil 包中,

$ vim $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 github.com/user/stringutil 目录中,则直接执行:

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

更多细节可以运行 go run test 和 查看 测试包文档

远程包

导入路径可以描述如何使用诸如Git之类的版本控制系统来获取包源代码。go工具使用此属性自动从远程仓库获取包。例如,本文档中描述的示例也保存在GitHub github.com/golang/example 上托管的Git仓库中。如果你在包的导入路径中包含仓库URL,那么go将自动获取,构建和安装它:

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

如果指定的包不在工作区, go get 将会通过 GOPATH 把它放到指定的工作区,如果包已经存在, go get 会跳过远程获取,其行为与 go install 相同。

上面 go get 之后的目录结构如下:

bin/
    hello                           # command executable
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 Wikigodoc.org提供了外部Go项目的列表。

有关使用go工具使用远程仓库的更多信息, 可以查看 go help importpath

原文地址:https://phpcasts.org/topics/47

如来神掌
809 声望107 粉丝

read and write