Go 语言相关介绍

Go 是一门年轻的语言。它在设计上借鉴了传统 C 语言的高性能特性,以及多种现代系统语言的优点,被认为是具有很大潜力的系统开发语言。要使用好 Go 语言,首先要掌握好相关的开发工具。
这里介绍如何快速安装和配置 Go 语言环境、选用合适的编辑器和 IDE,以及如何配合使用 Go 的配套开发工具来提高开发效率。

Go 语言环境安装十分简单,可以通过包管理器或自行下载方式进行,为了使用最新版本的 Go 环境,推荐大家通过下载环境包方式进行安装。

安装

首先,从 https://golang.org/dl/ 页面查看最新的软件包,并根据自己的平台进行下载,例如 Linux 环境下,目前最新的环境包为 https://storage.googleapis.com/golang/go1.13.linux-amd64.tar.gz

下载后,直接进行环境包的解压,存放到默认的 /usr/local/go 目录(否则需要配置 $GOROOT 环境变量指向自定义位置)下。

$ sudo tar -C /usr/local -xzf go1.13.linux-amd64.tar.gz

此时,查看 /usr/local/go 路径下,可能看到如下几个子目录。

  • API:Go API 检查器的辅助文件,记录了各个版本的 API 特性。
  • bin:Go 语言相关的工具的二进制命令。
  • doc:存放文档。
  • lib:一些第三方库。
  • misc:编辑器和开发环境的支持插件。
  • pkg:存放不同平台的标准库的归档文件(.a 文件)。
  • src:所有实现的源码。
  • test:存放测试文件。

安装完毕后,可以添加 Go 工具命令所在路径到系统路径,方便后面使用。创建 $GOPATH 环境变量,指向某个本地创建好的目录(如 $HOME/Go),作为后面 Go 项目的存放目录。如果使用 Go 模块模式,则无需进行这些配置。

添加如下环境变量到用户启动配置(如 $HOME/.bashrc)中。

export PATH=$PATH:/usr/local/go/bin
export GOPATH=$HOME/Go

其它更多平台下安装,可以参考 https://golang.org/doc/install

编辑器与 IDE

使用传统编辑器如 VIM,可以安装相应的 Golang 支持插件,如 vim-go

目前支持 Go 语言的 IDE(Integrated Development Environment) 还不是特别丰富。推荐使用 Jet Brains 出品的 Goland 或微软开发的 Visual Studio Code。

Goland 是专门针对 Go 语言设计的 IDE,在代码的补全、分析等方面性能更优越。可以从 https://www.jetbrains.com/go/ 下载获取。

Visual Studio Code 是一个通用的 IDE,可以通过安装支持 Go 的插件来进行开发。下载地址为 https://code.visualstudio.com/download。

此外,简单的代码逻辑验证也可以通过官方的在线 Playground 平台,地址为 https://play.golang.org/。

高效开发工具

Go 语言自带了不少高效的工具和命令,使用好这些工具和命令,可以很方便地进行程序的维护、编译和调试。

go doc 和 godoc

go doc 可以快速显示指定软件包的帮助文档。godoc 是一个类似的命令,功能更强大,它以 Web 服务的形式提供文档,即允许用户通过浏览器查看软件包的文档。可以通过如下命令进行快速安装。

$ go get golang.org/x/tools/cmd/godoc

godoc 命令使用格式如下。

$ godoc package [name ...]

比较有用的命令行参数包括:

  • -http=:PORT:指定监听的地址,默认是 :6060
  • -index:支持关键词索引。
  • -play:支持 Go 语言的 playground,用户可以在浏览器里面对 Go 语言进行测试。

例如,下面的命令将在本地快速启动一个类似 https://golang.org/ 的网站,包括本地软件包的文档和 playground 等。

$ godoc -http=:6060 -index -play

go build

编译软件包,例如编辑当前软件包内容。

$ go build .

支持如下参数:

  • -x 参数:可以打印出执行过程的详细信息,辅助调试。
  • -gcflags:指定编译器参数。
  • -ldflags:指定链接器参数,常见的可以通过 -X 来动态指定包变量值。

go clean

清理项目,删除编译生成的二进制文件和临时文件。使用格式如下

$ go clean

支持如下参数:

  • -i 参数:删除 go install 安装的文件。
  • -n 参数:打印删除命令,而不执行,方便进行测试检查。
  • -r 参数:递归清除,对依赖包也执行清理工作。
  • -x 参数:执行清除过程同时打印执行的删除命令,方便进行测试检查。

go env

打印与 go 相关的环境变量,命令使用格式如下。

$ go env [var ...]

例如,通过如下命令查看所有跟 go 相关的环境变量。

$ go env
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/opt/Go"
GORACE=""
GOROOT="/usr/local/go/1.8.3/libexec"
GOTOOLDIR="/usr/local/go/1.8.3/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/d8/3h28zg552853gpp7ymrxl2r80000gn/T/go-build128111214=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"
PKG_CONFIG="pkg-config"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"

go fmt 和 gofmt

两者都是对代码进行格式化检查和修正。go fmt 命令实际上是对 gofmt 工具进行了封装,默认调用 gofmt -l -w 命令。gofmt 命令的使用格式如下。

$ gofmt [flags] [path ...]

支持如下参数:

  • -d 参数:仅显示不符合格式规定的地方,不进行修正。
  • -e 参数:打印完整错误内容,默认是只打印 10 行。
  • -l 参数:列出不符合格式规定的文件路径。
  • -r 参数:重写的规则。
  • -s 参数:对代码尝试进行简化。
  • -w 参数:对不符合默认风格的代码进行修正。

go get

快速获取某个软件包并执行编译和安装,例如

$ go get github.com/hyperledger/fabric

支持如下参数:

  • -u 参数:可以强制更新到最新版。
  • -d 参数:仅获取软件包,不执行编译安装。

go install

对本地软件包执行编译,并将编译好的二进制文件安装到 $GOPATH/bin。等价于先执行 go build 命令,之后执行复制命令。

go list

列出本地包中的所有的导入依赖。命令格式为

$ go list [-e] [-f format] [-json] [build flags] [packages]

其中,-e 可以指定忽略出错的包。

go run

编译并直接运行某个主程序包。需要注意,该可以执行 go run 的程序包必须是主包,意味着包内必须有入口的主函数:main。

go test

执行软件包内带的测试用例(*_test.go 文件),例如递归执行当前包内所有的测试案例。

$ go test ./...

支持如下参数:

  • -v 参数:可以参数来打开详细测试日志,辅助调试。

golint

对代码进行格式风格检查,打印出不符合 Go 语言推荐风格的代码。安装该工具十分简单,通过如下命令即可快速安装。

$ go get -u github.com/golang/lint/golint

使用时,指定软件包路径即可,如对超级账本 Fabric 项目所有代码进行风格检查。

$ golint $GOPATH/src/github.com/hyperledger/fabric/...

注意后面的 ... 代表递归检查所有子目录下内容。

goimports

也是代码风格检查工具,重点在于对 imports 相关格式进行检查,比较强大的是能自动修正。安装该工具十分简单,通过如下命令即可快速安装。

$ go get golang.org/x/tools/cmd/goimports

使用时,也是指定软件包路径即可。另外,goimports 支持几个很有用的参数。

-d:仅显示修订,不实际写入文件。 -e:显示所有的错误。 -l:列出含有错误的文件路径。 -w:将修订直接写入文件,不显示出来。 -srcdir:指定对软件包进行查找的相对路径。

go vet

go vet 对代码的准确性进行基本检查,如函数调用参数缺失、不可达代码,或调用格式不匹配等。使用也十分简单,指定要检查的软件包路径作为参数即可。

go tool

go tool 命令中包括许多有用的工具子命令,包括 addr2line、API、asm、cgo、compile、cover、dist、doc、fix、link、nm、objdump、pack、pprof、trace。

其中,比较常用的包括 fix、trace 等。

  • fix 命令可以对自动对旧版本的代码进行升级修复,替换为使用新版本的特性。
  • trace 命令可以通过分析 trace 文件来追踪程序运行过程中的事件(包括 goroutine 的创建、使用和结束,以及系统调用和网络 IO 等底层事件),并提供图形化界面展示。例如如下命令会打开网页提供图形界面,展示程序执行情况。
$ go test -bench=. -trace trace.out
$ go tool trace trace.out

可以通过 go tool cmd -h 命令查看子目录具体支持的相关参数,在此不再赘述。

pprof 工具包

Go 语言自带了方便的性能分析工具,可以查看程序的 CPU、内存等在运行时的使用情况。

目前支持两种性能分析工具包。如果希望在程序执行过程中通过 Web 网页试试查看运行信息(go routine、堆栈等),可以导入 net/http/pprof 工具包,并在代码中启动 Web 服务,如下所示:

import (
    "http"
    _ "net/http/pprof"
)
func main() {
    // 提供 profiling web 界面 localhost:6060/debug/pprof
    go func() {
        http.ListenAndServe("localhost:6060", nil)
    }()
    // 应用程序代码
}

程序运行后,可以通过访问 localhost:6060/debug/pprof 路径来查看 go routine、thread、堆栈使用等实时信息。

如果是希望执行完毕后统一进行分析,可以使用 runtime/pprof 包,并在代码中启动性能分析功能。

例如,编写 main.go 文件,通过 go routine 来启动若干计时器,但并没有进行释放。

package main
import (
    "flag"
    "fmt"
    "log"
    "os"
    "runtime"
    "runtime/pprof"
    "time"
    "github.com/pkg/errors"
)
func testTimeout() error{
    incChan := make(chan int, 1)
    errChan := make(chan error, 1)
    timeout := 10 * time.Millisecond
    go func() {
        incChan <- 1
    }()
    select {
    case <-time.NewTicker(timeout).C:
        fmt.Println("Ticker")
        return errors.Errorf("Timed out waiting for connection message")
    case m := <-incChan:
        fmt.Printf("incChan: %d\n", m)
        return nil
    case err := <-errChan:
        fmt.Println("errChan")
        return errors.WithStack(err)
    }
}
var cpuprofile = flag.String("cpuprofile", "cpu.prof", "write cpu profile to `file`")
var memprofile = flag.String("memprofile", "mem.prof", "write memory profile to `file`")
func main() {
    // 启用 CPU profiling
    flag.Parse()
    if *cpuprofile != "" {
        f, err := os.Create(*cpuprofile)
        if err != nil {
            log.Fatal("could not create CPU profile: ", err)
        }
        if err := pprof.StartCPUProfile(f); err != nil {
            log.Fatal("could not start CPU profile: ", err)
        }
        defer pprof.StopCPUProfile()
    }
    // 应用代码
    i := 1
    for i = 1; i <= 100; i++ {
        fmt.Println(i)
        go testTimeout()
        time.Sleep(time.Duration(1) * time.Second)
    }
    // 导出内存统计
    if *memprofile != "" {
        f, err := os.Create(*memprofile)
        if err != nil {
            log.Fatal("could not create memory profile: ", err)
        }
        runtime.GC() // get up-to-date statistics
        if err := pprof.WriteHeapProfile(f); err != nil {
            log.Fatal("could not write memory profile: ", err)
        }
        f.Close()
    }
}

执行 go run main.go 编译和运行程序,观察到 CPU 使用率会逐步上升。

程序运行完成后,同一路径下会生成 cpu.prof 和 mem.prof 文件。其中记录了运行过程中的调用信息,之后可以通过 pprof 工具或较新版本的 go tool pprof 对其进行分析。这里以分析 CPU 使用为例。

说明:如果没有安装 pprof 工具,可以通过 go get -u github.com/google/pprof 快速安装。

pprof 工具最常见的功能是提供一个 Web 交互界面,供用户查看调用图、火焰图、CPU 消耗,可以通过如下命令打开 Web 操作界面。

$ pprof -http=localhost:6060 main cpu.prof

用户可以根据需要从不同角度查看运行信息,并分析程序消耗资源较多的环节。如下图所示。

pprof Web 界面

另外,runtime 包中也提供了如 runtime/trace 等工具包,可以生成 trace 文件供进行事件追踪分析,使用方法与 runtime/pprof 包类似,在此不再赘述。

依赖管理

govendor 工具

长期以来,Go 语言对外部依赖都没有很好的管理方式,只能从 $GOPATH 下查找依赖。这就造成不同用户在安装同一个项目时可能从外部获取到不同的依赖库版本,同时当无法联网时,无法编译依赖缺失的项目。

Golang 自 1.5 版本开始重视第三方依赖的管理,将项目依赖的外部包统一放到 vendor 目录下(类比 Nodejs 的 node_modules 目录),并通过 vendor.json 文件来记录依赖包的版本,方便用户使用相对稳定的依赖。

Daniel Theophanes 等人开发了 govendor 工具,方便对第三方依赖进行管理。

govendor 的安装十分简单,可以通过 go get 命令:

$ go get -u -v github.com/kardianos/govendor

对于 govendor 来说,主要存在三种位置的包:项目自身的包组织为本地(local)包;传统的存放在 $GOPATH 下的依赖包为外部(external)依赖包;被 govendor 管理的放在 vendor 目录下的依赖包则为 vendor 包。

具体来看,这些包可能的类型如下:

状态 缩写状态 含义
+local l 本地包,即项目自身的包组织
+external e 外部包,即被 $GOPATH 管理,但不在 vendor 目录下
+vendor v 已被 govendor 管理,即在 vendor 目录下
+std s 标准库中的包
+unused u 未使用的包,即包在 vendor 目录下,但项目并没有用到
+missing m 代码引用了依赖包,但该包并没有找到
+program p 主程序包,意味着可以编译为执行文件
+outside 外部包和缺失的包
+all 所有的包

常见的命令如下,格式为 govendor COMMAND

通过指定包类型,可以过滤仅对指定包进行操作。

命令 功能
init 初始化 vendor 目录
list 列出所有的依赖包
add 添加包到 vendor 目录,如 govendor add +external 添加所有外部包
add PKG_PATH 添加指定的依赖包到 vendor 目录
update 从 $GOPATH 更新依赖包到 vendor 目录
remove 从 vendor 管理中删除依赖
status 列出所有缺失、过期和修改过的包
fetch 添加或更新包到本地 vendor 目录
sync 本地存在 vendor.json 时候拉去依赖包,匹配所记录的版本
get 类似go get 目录,拉取依赖包到 vendor 目录

dep 工具

为了方便管理依赖,Go 团队 2016 年 4 月开始开发了 dep 工具,试图进一步简化在 Go 项目中对第三方依赖的管理。该工具目前已经被试验性支持,相信很快会成为官方支持的工具。

dep 目前需要 Go 1.7+ 版本,兼容其他依赖管理工具如 glide、godep、vndr、govend、gb、gvt、govendor、glock 等。

类似于 Govendor 工具,dep 将依赖都放在本地的 vendor 目录下,通过 Gopkg.toml 和 Gopkg.lock 文件来追踪依赖的状态。

  • Gopkg.toml 文件:手动编写或通过 dep init 命令生成。描述了项目对第三库的依赖规则,例如允许的版本范围等。用户可以通过编辑该文件表达预期的依赖控制目标。
  • Gopkg.lock 文件:通过 dep init 或 dep ensure 命令自动生成。根据项目代码和 Gopkg.toml 文件,计算出一个符合要求的具体的依赖关系并锁定,其中包括每个第三方库的具体版本。vendor 目录下的依赖库需要匹配这些版本。

安装可以通过 go get 命令:

$ go get -v -u github.com/golang/dep/cmd/dep

dep 使用保持简洁的原则,包括四个子命令。

  • init:对一个新的 Go 项目,初始化依赖管理,生成配置文件和 vendor 目录等;
  • status:查看当前项目依赖的状态,包括依赖包名称、限制范围、指定版本等。可以通过 -old 参数来只显示过期的依赖;
  • ensure:更新依赖,确保满足指定的版本条件。如果本地缺乏某个依赖,会自动安装;
  • version:显示 dep 工具的版本信息。

其中,ensure 命令最为常用,支持的子命令参数主要包括:

    • add:添加新的依赖,如 dep ensure -add github.com/pkg/foo@^1.0.0;
    • dry-run:模拟执行,打印参考改动但不实施;
    • no-vendor:根据计算结果更新 Gopkg.lock 文件,但不更新 vendor 中依赖包;
    • update:更新 Gopkg.lock 中的依赖到 Gopkg.toml 中允许的最新版本,默认同时更新 vendor 包中内容;
    • v:输出调试信息方面了解执行过程;
    • vendor-only:按照 Gopkg.lock 中条件更新 vendor 包中内容。

go module

Go 自 1.11 版本开始引入模块(module),在 1.13 版本中开始正式支持,以取代传统基于 $GOPATH 的方案。模块作为若干个包(package)的集合,带有语义化版本号,统一管理所有依赖。所有依赖模块缓存在 $GOPATH/pkg 目录下的 modsum 子目录中,未来计划迁移到 $GOCACHE 目录下。另外,不同项目的相同依赖模块全局只会保存一份,极大节约了存储空间。

模块需要两个配置文件,go.mod 和 go.sum。

前者管理项目中模块的依赖信息,可以通过 go mod init 命令生成;后者记录当前项目直接或间接依赖的所有模块的路径、版本、校验值等。

项目模块可以通过 go mod 子命令来显式操作,也会在编译、测试等命令中被隐式更新。

go mod 支持的子命令包括:

  • download:下载依赖模块到本地的缓存;
  • edit:编辑 go.mod 文件;
  • graph:查看当前的依赖结构;
  • init:初始化,创建 go.mod 文件;
  • tidy:整理依赖模块:添加新模块,并删除未使用模块;
  • vendor:将依赖模块复制到本地的 vendor 目录,方便兼容原来的 vendor 方式(该命令未来会遗弃);
  • verify:校验当前依赖是否正确,未被篡改;
  • why:解释为何需要某个依赖包。

基本使用过程为:

  • 使用 go mod init <module name> 来初始化本地的 go.mod 文件;
  • 使用 go get -u <package name>@<version> 来获取某个依赖包(不添加版本号会默认获取当前最新),同时自动更新 go.mod 文件。更新全部模块可以使用 go get -u all
  • 编译时使用 go build -mod=readonly 可以避免在编译过程中修改 go.mod;
  • 如果要使用本地的 vendor 目录进行编译,可以使用 go build -mod=vendor
  • 如果要检查可更新的依赖,可以使用 go list -m -u all。如果要执行更新,可以使用 go get -u
  • 此外,执行 go 相关命令(build、get、list、test 等)时,也会自动下载依赖并更新 go.mod 文件。

module 的开启可以通过 GO111MODULE=[auto|on|off] 等环境变量来控制。例如始终使用 go module,可以使用如下命令

$ go env -w GO111MODULE=on # 记录到 os.UserConfigDir 指定路径(默认为 $HOME/.config/go/)下的 env 文件中

此外,1.13 版本起,Go 还支持 GOPROXY 环境变量来指定拉取包的代理服务,GOPRIVATE 指定私有仓库地址。