Skip to content

包的管理和常用规范

包管理历程

大部分语言都有版本管理工具,比如nodejs的npm,python中的pip,java里的maven,但是go语言的版本管理经历了漫长的演进历程:

演进历程

Go 1.5以前,golang使用GOPATH方式管理代码。

  • 代码开发必须在 GOPATH 的 src 目录下。
  • 依赖包没有版本可言,都是指 master 最新代码。这个阶段只能手动管理依赖。

开始 Golang 这么设计是有原因的,因为 Google 是一个实践 Mono Repo(把所有的相关项目都放在一个仓库中)的公司。但更多的公司和组织更多的是用 Multi Repo(按模块分为多个仓库),GOPATH 至少解决了第三方源码依赖的问题,虽然它还不够完美。

Go1.5后,有人提出了external packages 的概念,于是就有了Go Vendor。

  • 解决了包依赖,用一个配置文件来管理。
  • 依赖包全都下载到项目vendor下,每个项目都把有一份,不能夸项目共享公共依赖。
  • 依赖包查找路径变成了 src/vendor ===> GOPATH。
  • 在这种模式下,会将第三方依赖的源码下载到本地,不同项目下可以有自己不同的vendor,依然没有解决版本化问题。
  • 默认是忽略vendor的,如果想在项目目录下有vendor可以执行 go vendor 命令,当然,如果构建程序的时候,希望使用vendor中的依赖 go build -mod vendor

Go1.12 版本后, 使用go modules进行包管理。

  • 不再以 GOPATH 为项目空间,使用 Modules 启用一个新的文件 go.mod 记录 module 的元信息,其中包含了依赖包的版本信息。
  • 项目内会生成一个 go.mod 文件,列出包依赖。所有来的第三方包会准确的指定版本号对于已经转移的包,可以用 replace 申明替换,不需要改代码。
  • 通过GO111MODULE控制,GO111MODULE 有三个值 off/on/auto(默认值),定义如下:
    • off:go命令行将不会支持 module 功能,寻找依赖包的方式将会沿用旧版本那种通过 vendor 目录或者 GOPATH 模式来查找。
    • on:go命令行会使用modules,而一点也不会去GOPATH目录下查找。
    • auto:默认值,当项目在 $GOPATH/src 外,且项目根目录有 go.mod 文件时,开启模块支持。
  • 当modules功能启用时,依赖包的存放位置变更为$GOPATH/pkg,允许同一个package多个版本并存,且多个项目可以共享缓存的module。

Vendor 介绍

为了解决 GOPATH 方式 三方依赖与项目都放在 GOPATH/src 下的问题,Go1.5之后,官方推出了 Go Vendor 的方式来管理项目。

go vendor 就是将依赖的包,特指外部包,复制到当前项目下的 vendor 目录下,这样 go build 的时候,go会优先从 vendor 目录寻找依赖包。

Go Vender 的出现解决了 GOPATH 不能多版本控制的问题。

Go Vender 放弃了依赖重用,使冗余度上升。同一个依赖包如果不同工程想复用,都必须各自复制一份在自己的 vendor 目录下。

Vendor 未解决的问题:无法精确的引用 外部包进行版本控制,不能指定引用某个特定版本的外部包,只是在开发时将其拷贝过来,但是一旦外部包升级,vendor 下面的包会跟着升级,而且 vendor 下面没有完整的引用包的版本信息, 对包升级带来了无法评估的风险。

Go Modules 介绍

大多数语言都有“依赖”、“包”等概念,Go语言的依赖处理经历了几次变革。

最早的时候,Go所依赖的所有的第三方库都放在 GOPATH 这个目录下面。从 go1.5 开始引入 vendor 模式,如果项目目录下有 vendor 目录,那么go工具链会优先使用 vendor 内的包进行编译、测试等。

go 1.11 开始,引入了 Go Modules 作为依赖解决方案,到 go 1.14 宣布 Go Modules 已经可以用于生产环境,到 go 1.16 版本开始 Go Module 默认开启。

Go modules 是 Go 语言的依赖解决方案,发布于 go1.11,成长于 go1.12,丰富于 go1.13,正式于 go1.14 推荐在生产上使用。

Go Modules使得Go语言开发者能够更方便地管理代码包及其版本,并能够与现有的版本控制工具(如Git、SVN等)集成使用。

在传统的 GOPATH 模式中,所有Go代码都必须位于一个全局的 GOPATH 路径之下,这使得在不同项目中使用不同版本的依赖包变得非常困难。然而,在 Go Modules 模式下,每个项目都可以独立管理自己的依赖关系,具有更好的兼容性。当使用 Go Modules 模式后,项目中会自动创建 go.mod 文件,其中记录了项目所依赖的模块及其版本信息。go.mod 是Go语言项目中的模块文件,用于管理项目的依赖关系和版本信息。

Go Modules 也支持语义化版本控制,这意味着开发者可以指定依赖包的版本范围,而不是仅仅依赖最新的版本。这种灵活性有助于确保项目的稳定性和可维护性。

Go moudles 目前集成在 Go 的工具链中,只要安装了 Go,自然而然也就可以使用 Go moudles 了,而 Go modules 的出现也解决了在 go1.11 前的几个常见争议问题:

  • Go 语言长久以来的依赖管理问题。
  • “淘汰”现有的 GOPATH 的使用模式。
  • 统一社区中的其它的依赖管理工具(提供迁移功能)。

优势:

  • 首先,研发者能够在任何目录下工作,而不仅仅是在 GOPATH 指定的目录。
  • 可以安装依赖包的指定版本,而不是只能从 main 分支安装最新的版本。
  • 可以导入同一个依赖包的多个版本。当我们老项目使用老版本,新项目使用新版本时会非常有用。
  • 要有一个能够罗列当前项目所依赖包的列表。这个的好处是当我们发布项目时不用同时发布所依赖的包。Go能够根据该文件自动下载对应的包。

命名规范

区分大小写

命名规则涉及变量、常量、全局函数、结构、接口、方法等的命名。 Go语言从语法层面进行了以下限定:任何需要对外暴露的名字必须以大写字母开头,不需要对外暴露的则应该以小写字母开头。

go文件名

文件名一律使用小写,不同单词之间用下划线分割, 命名应尽可能地见名知意。

go文件名一般以.go结尾,如:main.go、main_test.go、db.go、db_test.go 等。

包名

包名使用小写单词,不要使用下划线或者混合大小写。例如,包:tabwriter 不应该命名为 tabWriterTabWritertab_writer

示例:

go
import (
	"flag"
	"fmt"

	"github.com/BurntSushi/toml"
	"github.com/pingcap/errors"
)

变量

使用驼峰命名法(CamelCase)由有意义的英文单词拼接而成。比如说 UpperNamelowName 区别就是前者是大驼峰是导出的变量,后者是小驼峰是不可导出变量。

示例:

go
var userName string // 不可导出
var UserName string // 可导出

常量

常量均需使用全部大写字母组成,并使用下划线分词。

示例:

go
const MaxLength = 100
const PI = 3.14159

如果是枚举类型的常量,需要先创建相应类型:

go
type Scheme string

const (
    HTTP  Scheme = "http"
    HTTPS Scheme = "https"
)

结构体

采用驼峰命名法,首字母根据访问控制大写或者小写,例如:结构体 UserInfouserInfo,前者是所有包都可以访问,后者是当前包内可以访问。

Struct 申明和初始化格式采用多行,例如下面:

go
// 多行申明
type User struct{
    Username  string
    Email     string
}

// 多行初始化
u := User{
    Username: "astaxie",
    Email:    "[email protected]",
}

接收者命名

接收者变量名必须满足:短(通常是一两个字母的长度),类型本身的缩写,始终如一地应用于该类型的每个接收者。

长名称更好命名
func (tray Tray)func (t Tray)
func (info *ResearchInfo)func (ri *ResearchInfo)
func (this *ReportWriter)func (w *ReportWriter)
func (self *Scanner)func (s *Scanner)

示例:

go
type Person struct {
    Name string
    Age  int
}

// 这里的方法定义的指针变量p就是接收者
func (p *Person) ChangeName(newName string) {
    p.Name = newName
}

根据 MIT 许可证发布