0%

三:包与工具链

结束了上一章地狱般的折麽,从本章开始便重新回归胎教级别,如果说第二章的内容已经完全掌握,我觉得剩下的第3~5章基本可以略过了。

“包”的基础知识

如果连“包”的基本概念都不了解,那就先别往下看了,去学学Java的基础知识吧。本书没有过多概念性的解释,而是具体说明在GO语言中,如何定义和使用包。

1. 包的定义(命名)

包名应全部小写,每个.go文件都必须在第一行使用package <name>声明自己属于哪个包,同一个目录下不同的go源码必须声明为同一个包。此外,不同路径下的包名是可以相同的,因为导入包时采用的是全路径,路径本身可以区分不同的包。

另外,main包很特殊,如果一个工程内编译器没有找到main包,就不会创建可执行文件,main()函数也必须在main包中定义。

2. 包的引用(导入)

导入方式

go语言支持远程导入、本地导入和命名导入:

  • 本地导入——import "fmt"
  • 远程导入——import “github.com/spf13/viper”
  • 命名导入——import myFmt "fmt"

本地/远程导入,都是为了能愉快地使用别人已经写好的功能,不必重新发明轮子。而命名导入主要是为了方式重名包,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 两个buffer包名相同,存在冲突,调用时有歧义
import (
"request/buffer"
"response/buffer"
)
buffer.ToJson()

// 给不同的包命名,消除歧义
import (
买了否冷 "request/buffer"
否冷 "response/buffer"
)
买了否冷.ToJson()
否冷.ToImage()

// 顺带提一句,go使用utf8编码,虽然可以用中文符号,但不建议,这里仅仅是为了说明问题

此外,不管用哪种方式导入,编译器都会先从GO的安装路径$GOROOT中寻找,没有的话就是$GOPATH中查找

⚠️注意:
本地导入和远程导入从本质上讲没有区别。远程导入就是先从网上把第三方库下载到GOPATH当中,当作本地库import。

GOROOT和GOPATH

一个C/C++程序员(我是说我)比较容易犯的一个概念性错误——通过相对路径导入。举个例子:

如果某个工程源码结构是这样的:

1
2
3
4
5
project/
|---packages/
| |---/pkg1/...
| |---/pkg2/...
|---main.go

在上边👆的目录结构可以看到,packages目录和main.go文件在同一级目录下,如果习惯了C/C++的思想,会这样导入packages下面的包:

1
2
3
4
5
import (
"packages/pkg1"
"packages/pkg2"
)
// 如果不设置GOPATH,直接导入是不行的🙅🙅🙅!!

从相对路径的角度来看,这么干没问题,但我们忽略了GOROOT/GOPATH两个环境变量,GO语言只会从根据这两个环境变量去寻找包。

  • GOROOT通常是go语言安装路径,比如/usr/local/go/
  • GOPATH通常是用户自定义包路径

这种套路非常类似于Java项目开发中的JAVA_HOME、CLASSPATH的概念。

需要注意一点,go编译器会自动为这两个环境后追加src目录,也就是说工程依赖的包应该放在$GOPATH/src内。换而言之,上边的packages目录必须放置在project/src内,并将工程目录添加到GOPATH中。

3. 远程导入的坑

如果是远程导入,可以使用go get命令,下来源码中声明的“远程包”,它还会自动下载和更新各种依赖,但由于防火<哔哔>的缘故,这个命令大概率会timeout,只能曲线救国…

说明一下原因,当使用go get -v自动下载依赖包的时候,就可以看到整个过程,会默认去访问https://golang.org/x/<pkg>更新这些依赖,而golang.org的背后是一家名叫Google的公司在运营,由于我个遵纪守法的好公民,在我心目中只要我听不到看不到,它就不存在,是的,从小就在书本里学到一个成语——掩耳盗铃。

所以,我很奇怪,为什么go命令行要去访问一个根本不存在的网站,可能是个bug。好在机智如我,GitHub的GO仓库其实本质上就是golang.org/x 的镜像,所以务必记住这个镜像地址

👉 github.com/golang 👈

1
2
3
4
5
6
7
8
9
10
11
12
13
✍️划重点✍️
上面说了,远程导入无非是先把对应url的第三方库下载到GOPATH,既然自动下载不了,那为什么不手动在GOPATH中创建同名目录呢?

1. 我们可以在GOPATH下创建golang包的目录
mkdir -p $GOPATH/golang.org/x
2. 通过GitHub的go仓库,把需要的库克隆到这个目录,例如
git clone https://github.com/golang/sys.git

妈妈再也不用担心 import "golang.org/x/sys"

终极奥义:就是用第一个网址替换第二个
https://github.com/golang/PKG
https://golang.org/x/PKG

4. 使用下划线占位符

之前已经学习到,每个包内的init函数总是先于main函数执行,但前提是这个包在程序中被导入了

很多时候,我们需要执行包内的init()函数来初始化某些资源,但我们不会去直接调用包内的任何变量。由于GO的限制,不允许导入一个根本不曾使用的包

那么问题来了,如何导入一个包,却表面上不使用它?
import _ <package_name>是个不错的选择。

GO的工具链

对于我们这些Linux出身的猿类来说,敲命令来管理工程是非常有吸引力的,因为可以了解并掌握更多细节,自由度更高。

不论官方还是社区,go语言提供了非常丰富的命令,书中列举不是很多,我也觉得这种东西更适合直接看手册,我在此仅总结几个新手常用的,便于巩固记忆。

go

  • go build构建工程
  • go clean清理工程
  • go run <file.go>运行工程源码,其实是后台自动帮你完成构建
  • go doc <key>命令行文档查看器,包、函数、符号等具体使用规则

gb

作为go新手,我可能还没体验过大型项目那种复杂的依赖关系,但作为nodejs、java的实践者,我很清楚依赖管理的好处。不论是npm还是maven,它们对效率的提升是巨大的。

gb源自社区,目的是解决go的第三方依赖问题。试想一下,对于一个大型项目,几十上百个第三方库,不同的版本,库与库之间的藕断丝连,一个个去筛查定位,无疑是在浪费生命。

gb其实就是在工程目录下,创建一个vendor目录,里面存放各种需要的依赖库,gb会自动下载、更新这些库文件,不用开发者操心(总之用过npm/maven的人都懂,懒得解释了)。

自己的工程目录就放在src目录下,不会和第三方的依赖有刮扯,需要构建工程的时候,仅需gb build all,搞定!

小结一下

  1. GO包管理采用绝对路径,务必注意GOROOT和GOPATH环境变量
  2. GO有本地、远程、命名三种导入包的方式
  3. 远程导入可能会timeout,记得替换为GitHub的镜像仓库
  4. 仅需要执行包内的init函数时,可以使用下划线占位符
  5. go命令的build/clean/run/doc是很常用的命令工具
  6. 学会采用依赖管理工具来管理项目,如gb
小小鼓励,大大心意!