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 不应该命名为 tabWriter 、 TabWriter 或 tab_writer 。

示例:

go
import (
	"flag"
	"fmt"

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

变量 ​

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

示例:

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"
)

结构体 ​

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

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

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

// 多行初始化
u := User{
    Username: "astaxie",
    Email:    "astaxie@gmail.com",
}

接收者命名 ​

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

长名称更好命名
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 许可证发布