GO语言学习笔记-包结构篇 Study for Go ! Chapter eight - Package Structure

持续更新 Go 语言学习进度中 ......

  1. GO语言学习笔记-类型篇 Study for Go! Chapter one - Type - slowlydance2me - 博客园 (cnblogs.com)
  2. GO语言学习笔记-表达式篇 Study for Go ! Chapter two - Expression - slowlydance2me - 博客园 (cnblogs.com)
  3. GO语言学习笔记-函数篇 Study for Go ! Chapter three - Function - slowlydance2me - 博客园 (cnblogs.com)

Study for Go ! Chapter eight - Package Structure

1. Workspace

  • 工作空间主要由 src、bin、pkg 三个目录组成。通常需要将空间路径添加到 GOPATH 环境变量列表中,一遍相关工具能正常工作

  • 在工作空间里,包括子包在内的所有源码文件都保存在 src 目录下。至于bin、pkg两个目录,其主要影响 go install/get 命令,它们会将编译结果 (可执行文件或静态库)安装到这两个目录下,以实现增量编译

环境变量

  • 编译器等相关工具按 GOPATH 设置的路径搜索目标。也就是说在导入目标库时,排在列表前面的路径比当前工作空间优先级更高

  • 另外,go get 默认将下载的第三方包保存到列表中第一个工作空间内

  • 环境变量 GOROOT 用于指示工具链和标准库的存放位置。在生成工具链时,相关路径就已经嵌入到可执行文件内,故无须格外设置,但如果出现类似下面这样的错误提示,请检查路径是否一致

  • 除通过设置 GOROOT 环境变量覆盖内部路径外,还可以移动目录 (改名、符号连接等),或重新编译工具链来解决

  • 至于 GOBIN ,则是强制代替工作空间的 bin 目录,作为 go install 目标保存路径,这可避免将所有工作空间的 bin 路径 添加到 PATH 环境变量中

  • 在使用 Git 等版本控制工具时,建议忽略 pkg、bin 目录。直接在 src, 或具体的子包下创建代码仓库 (repository)

2. 导入包

  • 使用标准库或第三方包前,需使用 import 导入,参数是工作空间中以 src 为七十的绝对路径。编译器从标准库开始搜索,然后依次搜索 GOPATH 列表中的各个工作空间

  • 除了使用默认包名外还可以使用别名,以解决同名冲突问题

Attention;

  • import 导入参数是路径,而非包名,尽管习惯将包和目录名保持一致,但这不是强制规定,在代码中引用包成员时,使用包名而非目录名

  • 有四种导入方式

    • 默认方式

    • 别名方式

    • 简便方式 (常用于单元测试代码中,不推荐在正式项目于代码中使用。)

    • 初始化方式 (无法引用,仅用来初始化目标包,让目标包的初始化函数得以执行)

    不能直接或者间接导入自己,不支持任何形式的循环导入

  • 未使用的导入(不包含初始化方式)会被编译器视为错误

    相对路径

    • 除了工作空间和绝对路径外,部分工具还支持相对路径。可在非工作空间目录下,直接运行,编译一些测试代码

    • 相对路径是指:当前目录,或以“ ./ ” 和 “ ../ ” 开头的路径

    • 不管是否在 test 目录下,只要命令行路径正确,就可以用go build/run/test 进行编译,运行或测试。但因缺少工作空间相关目录,go install 会无法工作

    • 在设置了 GOPATH 的工作空间中,相对路径会导致编译失败

    • go run 不受此影响,可正常执行

    自定义路径

    • 即便将代码托管在 GitHub ,但我们依然希望使用自有域名定义下载和导入路径。方法很简单,在 Web 服务器对应路径返回中包含 “ go-import ” 跳转信息即可

    • 使用唯一的导入路径,方便日后迁移存储端,但此方法对 vendor 机制无效

3. 组织结构

  • 包由一个或多个保存在同一目录下(不含子目录)的源码文件组成。包的用途类似名字空间(namespace),是成员作用域和访问权限的边界

  • 包名和目录名并无关系,不要求保持一致

  • 包名常用单数形式

  • 源码文件必须使用 UTF-8 格式,否则会导致编译出错

  • 同一目录下所有源码文件必须使用相同包名称,因导入是使用绝对路径,所以在搜索路径下,包必须有唯一路径,但无须是唯一名字

    有几个被保留、有特殊含义的包名称;

    • main:可执行入口(入口函数 mian.mian)

    • all:标准库以及 GOPATH 中能找到的所有包

    • std,cmd:标准库及工具链

    • documentation:储存文档信息,无法导入(和目录名无关)

    (相关工具忽略以” . “ 或 ” _ “ 开头的目录或文件,但是又允许导入保存在这些目录中的包! )

    权限

    • 所有成员在包内均可访问,无论是否在同一源码文件中。但只有名称首字母大写的为可导出成员,在包外可视 (该规则适用于全局变量、全局常量、类型、结构字段、函数、方法等)

    • 可通过指针转换等方式绕开该限制

    初始化

    • 包内每个源码文件都可定义一到多个初始化函数,但编译器不保证执行次序。

    • 实际上,所有这些初始化函数(包括标准库和导入的第三方包)都由编译器自动生成的一个包装函数进行调用,因此可保证在单一线程上执行,且只执行一次

    • 编译器首先确保完成所有全局变量初始化,然后才开始执行初始化函数,知道这些全部结束后,运行时才正式进入 main.main 入口函数

    • 可以在初始化函数中创建 goroutine,或等到它执行结束

    • 如果在多个初始化函数中引用全局变量,那么最好在变量定义出直接赋值,因无法保证执行次序,所以任何初始化函数中的赋值都有可能“ 延迟无效 ”

    延迟包

    • 在进行代码重构时,我们会将一些内部模块陆续分离出来,以独立包形式维护。此时,基于首字母大小写的访问权限控制就显得过于粗犷。因为我们希望这些包导出成员仅在特定范围内访问,而不是向所有用户公开

    • 内部包机制相当于增加了新的访问权限控制:所有保存在 internal 目录下的包 ( 包括自身 )仅能被其父目录下的包 ( 含所有层次的子目录 )访问

    • 导入内部包必须使用完整路径

4. 依赖管理

  • 如何管理和保存第三方包,一致存在争议。将项目所有的第三方依赖都放到一个独立工作空间中,可能会导致版本冲突。放到项目工作空间,又会把工作目录搞的面目全非。为此,引入了名为 vendor 的机制,专门存放第三方包,实现将源码和依赖完整打包分发

  • 如果说 internal 针对内部,那么 vender 就是 针对外部 (external)

  • 导入 vendor 中的第三方包,参数是以 vendor/ 为起点的绝对路径。这就避免了 vendor 目录位置带来的麻烦,让导入无论使用 vender,还是 GOPATH 都能保持一致

ATTENTION

  • vendor 比标准库优先级更高

question:当多个 vendor 目录嵌套时,如何正确查找目标 ?要知道引入的第三方包也可能存在有自己的 vendor 依赖目录

answer:从当前源文件所在目录开始,逐级向上构造 vendor 全路径,知道发现路径匹配的目标为止。匹配失败,则依旧搜索 GOPATH

  • 要使用 vendor机制,须开启 ” GO15VENDOREXPERIMENT=1 “ 环境变量开关 ( GO 1.6 以上默认开启 )且必须是设置了 GOPATH 的工作空间

使用 go get 下载第三方包时,依旧使用 GOPATH 第一个工作空间,而非 vendor 目录。当前工具链中并没有真正意义上的包管理依赖,好在有不少的第三方工具可以选择