build

介绍
  1. Go语言中使用 go build 命令用于编译代码。在包的编译过程中,若有必要,会同时编译与之相关联的包。
  2. Go语言的编译速度非常快。Go 1.9 版本后默认利用Go语言的并发特性进行函数粒度的并发编译。
  3. go build 有很多种编译方法,如无参数编译、文件列表编译、指定包编译等。
  4. 还可以附加参数,按使用频率排列,可以显示更多的编译信息和更多的操作:


-v 编译时显示包名
-o 编译时指定可执行文件名
-p n 开启并发编译,默认情况下n值为 CPU 逻辑核数
-a 强制重新构建
-n 打印编译时会用到的所有命令,但不编译
-x 打印编译时会用到的所有命令,并编译
-race 开启竞态检测,竞态检测


无参数编译

介绍
  1. 该模式会自动获得需要编译的包进行编译。也是我们最常用的编译方式。
    示例
    目录关系:
    1. .
    2. └── src
    3. └── chapter11
    4. └── gobuild
    5. ├── lib.go
    6. └── main.go
    main.go:
    1. package main
    2. import (
    3. "fmt"
    4. )
    5. func main() {
    6. // 同包的函数
    7. pkgFunc()
    8. fmt.Println("hello world")
    9. }
    lib.go:
    1. package main
    2. import "fmt"
    3. func pkgFunc() {
    4. fmt.Println("call pkgFunc")
    5. }
    编译
    1. $ cd src/chapter11/gobuild/
    2. $ go build
    3. $ ls
    4. gobuild lib.go main.go
    5. $ ./gobuild
    6. call pkgFunc
    7. hello world

文件列表编译

介绍
  1. 编译同目录的多个源码文件时,可以在 go build 的后面提供多个文件名。该方式更适合使用Go语言编写的只有少量文件的工具。
  2. 编译后的可执行文件名默认为文件列表中第一个源码文件名。如果需要指定输出可执行文件名,使用-o参数。
  3. 文件列表中的每个文件必须是同一个包的 Go 源码。也就是说,不能像 C++ 一样将所有工程的 Go 源码使用文件列表方式进行编译。编译复杂工程时需要用“指定包编译”的方式。

    1. go build file1.go file2.go……

    示例

    目录结构同无参数编译。

    1. $ go build -o myexec main.go lib.go
    2. $ ls
    3. lib.go main.go myexec
    4. $ ./myexec
    5. call pkgFunc
    6. hello world

    指定包编译

    介绍
  4. 设置 GOPATH 后,可以直接根据包名进行编译,即便包内文件被增(加)删(除)也不影响编译指令。

    示例

    目录关系:

    1. .
    2. └── src
    3. └── chapter11
    4. └──goinstall
    5. ├── main.go
    6. └── mypkg
    7. └── mypkg.go

    main.go:

    1. package main
    2. import (
    3. "chapter11/goinstall/mypkg"
    4. "fmt"
    5. )
    6. func main() {
    7. mypkg.CustomPkgFunc()
    8. fmt.Println("hello world")
    9. }

    mypkg.go:

    1. package mypkg
    2. import "fmt"
    3. func CustomPkgFunc() {
    4. fmt.Println("call CustomPkgFunc")
    5. }

    编译

    1. $ export GOPATH=/home/davy/golangbook/code
    2. $ go build -o main chapter11/goinstall
    3. $ ./goinstall
    4. call CustomPkgFunc
    5. hello world

run

介绍
  1. 该命令会编译源码,并且直接执行源码的 main() 函数。
  2. 该命令不会在当前目录下生成任何文件,可执行文件被放在临时文件中被执行,工作目录被设置为当前目录。
  3. 在该命令后部可以添加参数,这部分参数会作为代码可以接受的命令行输入提供给程序。
    示例
    1. package main
    2. import (
    3. "fmt"
    4. "os"
    5. )
    6. func main() {
    7. fmt.Println("args:", os.Args)
    8. }
    1. $ go run main.go --filename xxx.go
    2. args: [/tmp/go-build006874658/command-line-arguments/_obj/exe/main--filename xxx.go]

install

介绍
  1. 同 build 命令类似,附加参数绝大多数都可以与 go build 通用,无法使用-o附加参数进行自定义。
  2. 该命令是建立在 GOPATH 上的,无法在独立的目录里使用 go install。
  3. 这个命令在内部实际上分成了两步操作:
    1. 第一步是生产编译的中间文件放在 GOPATH 的 pkg 目录下(.a包)。
    2. 第二步会把编译好的执行文件放到 GOPATH 的 bin 目录。可执行文件的名称来自于编译时的包名。
      示例
      1. $ export GOPATH=/home/davy/golangbook/code
      2. $ go install chapter11/goinstall
      编译完成后的目录结构如下:
      1. .
      2. ├── bin
      3. └── goinstall
      4. ├── pkg
      5. └── linux_amd64
      6. └── chapter11
      7. └── goinstall
      8. └── mypkg.a
      9. └── src
      10. └── chapter11
      11. ├── gobuild
      12. ├── lib.go
      13. └── main.go
      14. └── goinstall
      15. ├── main.go
      16. └── mypkg
      17. └── mypkg.go

clean

介绍
  1. 命令就像 Java 中的maven clean命令一样,会清除掉编译过程中产生的一些文件。移除当前源码包和关联源码包里面编译生成的文件,这些文件包括以下几种:
  • 执行go build命令时在当前目录下生成的与包名或者 Go 源码文件同名的可执行文件。在 Windows 下,则是与包名或者 Go 源码文件同名且带有“.exe”后缀的文件。
  • 执行go test命令并加入-c标记时在当前目录下生成的以包名加“.test”后缀为名的文件。在 Windows 下,则是以包名加“.test.exe”后缀的文件。
  • 执行go install命令安装当前代码包时产生的结果文件。如果当前代码包中只包含库源码文件,则结果文件指的就是在工作区 pkg 目录下相应的归档文件。如果当前代码包中只包含一个命令源码文件,则结果文件指的就是在工作区 bin 目录下的可执行文件。
  • 在编译 Go 或 C 源码文件时遗留在相应目录中的文件或目录 。包括:“_obj”和“_test”目录,名称为“_testmain.go”、“test.out”、“build.out”或“a.out”的文件,名称以“.5”、“.6”、“.8”、“.a”、“.o”或“.so”为后缀的文件。这些目录和文件是在执行go build命令时生成在临时目录中的。
  1. 实际开发中该命令使用的可能不是很多,一般都使用命令清除编译文件,然后再将源码递交到 github 上。
  2. 支持参数: | -i | 清除关联的安装的包和可运行文件,也就是通过go install安装的文件 | | —- | —- | | -n | 把需要执行的清除命令打印出来,但不真正执行 | | -x | 把需要执行的清除命令打印出来,并执行 | | -r | 循环的清除在 import 中引入的包 | | -cache | 删除所有go build命令的缓存 | | -testcache | 删除当前包所有的测试结果 |

示例
  1. go clean -x
  2. rm -f code code.exe code.test code.test.exe main main.exe

fmt

介绍
  1. Go语言的开发团队制定了统一的官方代码风格,并且推出了 gofmt 工具来帮助开发者格式化他们的代码到统一的风格。
  2. gofmt 是一个 cli 程序,会优先读取标准输入,如果传入了文件路径的话,会格式化这个文件,如果传入一个目录,会格式化目录中所有 .go 文件,如果不传参数,会格式化当前目录下的所有 .go 文件。
  3. gofmt 使用 tab 来表示缩进,并且对行宽度无限制,如果手动对代码进行了换行,gofmt 不会强制把代码格式化回一行。
  4. 参数如下: | 标记名称 | 标记描述 | | —- | —- | | -l | 仅把那些不符合格式化规范的、需要被命令程序改写的源码文件的绝对路径打印到标准输出。而不是把改写后的全部内容都打印到标准输出。 | | -w | 把改写后的内容直接写入到文件中,而不是作为结果打印到标准输出。 | | -r | 添加形如“a[b:len(a)] -> a[b:]”的重写规则。如果我们需要自定义某些额外的格式化规则,就需要用到它。 | | -s | 简化文件中的代码。在默认情况下,该值为false | | -d | 只把改写前后内容的对比信息作为结果打印到标准输出。而不是把改写后的全部内容都打印到标准输出。
    命令程序将使用 diff 命令对内容进行比对。在 Windows 操作系统下可能没有 diff 命令,需要另行安装。 | | -e | 打印所有的语法错误到标准输出。如果不使用此标记,则只会打印每行的第 1 个错误且只打印前 10 个错误。 | | -comments | 是否保留源码文件中的注释。在默认情况下,此标记会被隐式的使用,并且值为 true。 | | -tabwidth | 此标记用于设置代码中缩进所使用的空格数量,默认值为 8。要使此标记生效,需要使用“-tabs”标记并把值设置为 false。 | | -tabs | 是否使用 tab(’\t’)来代替空格表示缩进。在默认情况下,此标记会被隐式的使用,并且值为 true。 | | -cpuprofile | 是否开启 CPU 使用情况记录,并将记录内容保存在此标记值所指的文件中。 |

-s 简化

去除数组、切片、Map 初始化时不必要的类型声明
  1. []T{T{}, T{}}
  2. []T{{}, {}}

去除数组切片操作时不必要的索引指定
  1. s[a:len(s)]
  2. s[a:]

去除循环时非必要的变量赋值
  1. for x, _ = range v {...}
  2. for x = range v {...}
  1. for _ = range v {...}
  2. for range v {...}

-f 重写

示例规则:

  1. gofmt -w -r "a + b -> b + a" main.go
  1. func main() {
  2. a := 1
  3. b := 2
  4. c := a + b
  5. fmt.Println(c)
  6. }
  7. func main() {
  8. a := 1
  9. b := 2
  10. c := b + a
  11. fmt.Println(c)
  12. }

go fmt
  1. Go语言中还有一个go fmt 命令,go fmt 命令是 gofmt 的简单封装。
  2. go fmt 在调用 gofmt 时添加了-l -w参数,相当于执行了gofmt -l -w
  3. 该命令本身只有两个可选参数。如果需要更细化的配置,需要直接执行 gofmt 命令。
  • -n仅打印出内部要执行的go fmt的命令;
  • -x命令既打印出go fmt命令又执行它。

get

⚠️:该命令已过时,推荐使用 go module (version>= 1.13)来处理依赖。见:go module

介绍
  1. go get 命令可以借助代码管理工具通过远程拉取或更新代码包及其依赖包,并自动完成编译和安装。
  2. 这个命令可以动态获取远程代码包,在使用 go get 命令前,需要安装与远程包匹配的代码管理工具,下载源码包的 go 工具会自动根据不同的域名(也支持自定义域名)调用不同的源码工具(并同时把这些命令加入你的 PATH 中): | BitBucket | Mercurial Git | | —- | —- | | GitHub | Git | | Google Code Project Hosting | Git, Mercurial, Subversion | | Launchpad | Bazaar |

  3. 该命令参数中需要提供一个包名。运行后需要两步:第一步是下载源码包,第二步是执行 go install。

  4. 参数介绍: | -v | 显示操作流程的日志及信息,方便检查错误 | | —- | —- | | -d | 只下载不安装 | | -u | 强制使用网络去下载丢失的包,但不会更新已经存在的包 | | -f | 只有在你包含了 -u 参数的时候才有效,不让 -u 去验证 import 中的每一个都已经获取了,这对于本地 fork 的包特别有用 | | -t | 同时也下载需要为运行测试所需要的包 | | -fix | 在获取源码之后先运行 fix | | -insecure | 允许使用不安全的 HTTP 方式进行下载操作 |

示例

使用前,请确保 GOPATH 已经设置。Go 1.8 版本之后,GOPATH 默认在用户目录的 go 文件夹下。

  1. go get github.com/davyxu/tabtoy

pprof

介绍
  1. 工具链中的 go pprof 可帮助开发者快速分析及定位各种性能问题,如 CPU 消耗、内存分配及阻塞分析。
  2. 性能分析首先需要使用 runtime.pprof 包嵌入到待分析程序的入口和结束处。runtime.pprof 包在运行时对程序进行每秒 100 次的采样,最少采样 1 秒。然后将生成的数据输出,让开发者写入文件或者其他媒介上进行分析。
  3. go pprof 工具链配合 Graphviz 图形化工具可以将 runtime.pprof 包生成的数据转换为 PDF 格式,以图片的方式展示程序的性能分析结果。


Graphviz
  1. Graphviz 是一套通过文本描述的方法生成图形的工具包。描述文本的语言叫做 DOT。
  2. www.graphviz.org 网站可以获取到最新的 Graphviz 各平台的安装包。
  3. CentOS 下,可以使用 yum 指令直接安装: yum install graphiviz


profile
  1. runtime.pprof 提供基础的运行时分析的驱动,但是这套接口使用起来还不是太方便,例如:
    • 输出数据使用 io.Writer 接口,虽然扩展性很强,但是对于实际使用不够方便,不支持写入文件。
    • 默认配置项较为复杂。
  2. 很多第三方的包在系统包 runtime.pprof 的技术上进行便利性封装,让整个测试过程更为方便。这里使用 github.com/pkg/profile 包进行例子展示: go get github.com/pkg/profile


示例

下面代码故意制造了一个性能问题,同时使用 github.com/pkg/profile 包进行性能分析。

  1. package main
  2. import (
  3. "github.com/pkg/profile"
  4. "time"
  5. )
  6. func joinSlice() []string {
  7. var arr []string
  8. for i := 0; i < 100000; i++ {
  9. // 故意造成多次的切片添加(append)操作, 由于每次操作可能会有内存重新分配和移动, 性能较低
  10. arr = append(arr, "arr")
  11. }
  12. return arr
  13. }
  14. func main() {
  15. // 开始性能分析, 返回一个停止接口
  16. stopper := profile.Start(profile.CPUProfile, profile.ProfilePath("."))
  17. // 在main()结束时停止性能分析
  18. defer stopper.Stop()
  19. // 分析的核心逻辑
  20. joinSlice()
  21. // 让程序至少运行1秒
  22. time.Sleep(time.Second)
  23. }

编译并运行:

  1. // 编译
  2. $ go build -o cpu cpu.go
  3. // 运行,在当前目录输出 cpu.pprof 文件
  4. $ ./cpu
  5. // 使用 go tool 工具链输入 cpu.pprof 和 cpu 可执行文件,生成 PDF 格式的输出文件,将输出文件重定向为 cpu.pdf 文件。
  6. // 这个过程中会调用 Graphviz 工具,Windows 下需将 Graphviz 的可执行目录添加到环境变量 PATH 中。
  7. $ go tool pprof --pdf cpu cpu.pprof > cpu.pdf

最终生成 cpu.pdf 文件,使用 PDF 查看器打开文件,观察后发现下图所示的某个地方可能存在瓶颈:
image.png
图中的每一个框为一个函数调用的路径,第 3 个方框中 joinSlice 函数耗费了 50% 的 CPU 时间,存在性能瓶颈。重新优化代码,在已知切片元素数量的情况下直接分配内存,代码如下:

  1. func joinSlice() []string {
  2. const count = 100000
  3. var arr []string = make([]string, count)
  4. for i := 0; i < count; i++ {
  5. arr[i] = "arr"
  6. }
  7. return arr
  8. }

重新运行上面的代码进行性能分析,最终得到的 cpu.pdf 中将不会再有耗时部分。


generate

介绍
  1. go generate命令是在Go语言 1.4 版本里面新添加的一个命令,当运行该命令时,它将扫描与当前包相关的源代码文件,找出所有包含//go:generate的特殊注释,提取并执行该特殊注释后面的命令。
  2. 使用该命令时有以下几点需要注意:
    • 该特殊注释必须在 .go 源码文件中;
    • 每个源码文件可以包含多个 generate 特殊注释;
    • 运行go generate命令时,才会执行特殊注释后面的命令;
    • go generate命令执行出错时,将终止程序的运行;
    • 特殊注释必须以//go:generate开头,双斜线后面没有空格。
  3. 在下面这些场景下,我们会使用go generate命令:
    • yacc:从 .y 文件生成 .go 文件;
    • protobufs:从 protocol buffer 定义文件(.proto)生成 .pb.go 文件;
    • Unicode:从 UnicodeData.txt 生成 Unicode 表;
    • HTML:将 HTML 文件嵌入到 go 源码;
    • bindata:将形如 JPEG 这样的文件转成 go 代码中的字节数组。
    • string 方法:为类似枚举常量这样的类型生成 String() 方法;
    • 宏:为既定的泛型包生成特定的实现,比如用于 ints 的 sort.Ints。
  4. 执行go generate命令时,也可以使用一些环境变量,如下所示:
    • $GOARCH 体系架构(arm、amd64 等);
    • $GOOS 当前的 OS 环境(linux、windows 等);
    • $GOFILE 当前处理中的文件名;
    • $GOLINE 当前命令在文件中的行号;
    • $GOPACKAGE 当前处理文件的包名;
    • $DOLLAR 固定的$,不清楚具体用途。

go generate命令格式如下所示:

  1. go generate [-run regexp] [-n] [-v] [-x] [command] [build flags] [file.go... | packages]

参数说明如下:

  • -run 正则表达式匹配命令行,仅执行匹配的命令;
  • -v 输出被处理的包名和源文件名;
  • -n 显示不执行命令;
  • -x 显示并执行命令;
  • command 可以是在环境变量 PATH 中的任何命令。

示例
  1. package main
  2. import "fmt"
  3. //go:generate go run main.go
  4. //go:generate go version
  5. func main() {
  6. fmt.Println("http://c.biancheng.net/golang/")
  7. }

运行:

  1. go generate -x
  2. go run main.go
  3. http://c.biancheng.net/golang/
  4. go version
  5. go version go1.13.6 windows/amd64

通过运行结果可以看出//go:generate之后的命令成功运行了。


test

test 测试