11

如何正确的开始用 Go 编程

本文会演示简单的Go软件包的开发过程,并介绍了go命令行工具,这是我们获取,构建和安装Go软件包和命令的标准方法。

go工具要求你以特定方式组织代码。我们会介绍Go安装启动和运行的最简单方法,一定要仔细阅读啊。

组织代码结构

概要

  • Go 程序员一般会将他们的源代码存放在一个工作区中(多个项目放在一个工作区)
  • 工作区中包含许多由 git 管理的代码仓库(也可以是其他版本控制工具管理的)
  • 每个代码仓库包含一个或者多个 Go package
  • 每个 package 由单一目录下的一个或多个Go 源码文件组成
  • package 的目录路径决定了其导入路径

与其他编程语言不同的是,在其他编程语言里每个项目都有自己的工作区,并且工作区都与版本控制系统紧密相关。

工作区

工作区是一个目录层级,这个目录层级在顶层有两个目录:

  1. src 目录,存放源代码文件。
  2. 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

WX20191117-152623@2x.png


Kevin
5k 声望1.5k 粉丝