3.1 Go Modules的入门
3.1.1 Go Module最初版本--GOPATH
可以将其理解为工作目录:
每个目录存放的文件都是不相同的:
- bin:存放编译以后生成的二进制可执行文件
- pkg:存放编译后生成的.a文件
- src:存放项目的源代码,可以是自己写的代码,也可以是go get下载的包
将自己的包或者别人的包全部放在$GOPATH/src目录下进行管理的方式,我们称之为GOPATH模式
在这个模式下,使用 go install 时,生成的可执行文件会放在 $GOPATH/bin 下
如果你安装的是一个库,则会生成 .a 文件到 $GOPATH/pkg 下对应的平台目录中(由 GOOS 和 GOARCH 组合而成),生成 .a 为后缀的文件。
GOOS,表示的是目标操作系统,有 darwin(Mac),linux,windows,android,netbsd,openbsd,solaris,plan9 等,而 GOARCH,表示目标架构,常见的有 arm,amd64 等,这两个都是 go env 里的变量,你可以通过 go env 变量名 进行查看。
使用GOPATH进行开发遇到的问题:
- 你无法在你的项目中,使用指定版本的包,因为不同版本的包的导入方法也都一样
- 其他人运行你的开发的程序时,无法保证他下载的包版本是你所期望的版本,当对方使用了其他版本,有可能导致程序无法正常运行
- 在本地,一个包只能保留一个版本,意味着你在本地开发的所有项目,都得用同一个版本的包,这几乎是不可能的。
3.1.2 go vendor模式
go vender解决了GOPATH方案下不同项目下无法使用多个版本库的问题。
解决的思路就是,在每个项目下都创建一个 vendor 目录,每个项目所需的依赖都只会下载到自vendor目录下,项目之间的依赖包互不影响。在编译时,v1.5 的 Go 在你设置了开启GO15VENDOREXPERIMENT=1 (注:这个变量在 v1.6 版本默认为1,但是在 v1.7 后,已去掉该环境变量,默认开启 vendor 特性,无需你手动设置)后,会提升 vendor 目录的依赖包搜索路径的优先级(相较于 GOPATH)
其搜索包的优先级顺序,由高到低是这样的:
- 当前包下的 vendor 目录
- 向上级目录查找,直到找到 src 下的 vendor 目录
- 在 GOROOT 目录下查找
- 在 GOPATH 下面查找依赖包
go vender存在的问题:
- 如果多个项目用到了同一个包的同一个版本,这个包会存在于该机器上的不同目录下,不仅对磁盘空间是一种浪费,而且没法对第三方包进行集中式的管理(分散在各个角落)。
- 并且如果要分享开源你的项目,你需要将你的所有的依赖包悉数上传,别人使用的时候,除了你的项目源码外,还有所有的依赖包全部下载下来,才能保证别人使用的时候,不会因为版本问题导致项目不能如你预期那样正常运行。
3.1.3 go mod模式
GO111MODULE 是一个开关,通过它可以开启或关闭 go mod 模式。
它有三个可选值:off、on、auto,默认值是auto
- GO111MODULE=off禁用模块支持,编译时会从GOPATH和vendor文件夹中查找包。
- GO111MODULE=on启用模块支持,编译时会忽略GOPATH和vendor文件夹,只根据 go.mod下载依赖。
- GO111MODULE=auto,当项目在$GOPATH/src外且项目根目录有go.mod文件时,自动开启模块支持
如果希望继续使用之前的包依赖管理方案,设置如下:
$ go env -w GO111MODULE="off" //如果想要开启,将off替换成on即可
go mod依赖的管理
go mod 不再依靠 $GOPATH,使得它可以脱离 GOPATH 来创建项目,于是我们在家目录下创建一个 go_test 的目录,用来创建我的项目,详细操作如下:
接下来,进入项目目录,执行如下命令进行 go modules 的初始化
go install 把下载的包安装到哪里了?
在使用 go modules 模式后,项目目录下会多生成两个文件也就是 go.mod 和 go.sum
go.mod文件
- 第一行:模块的引用路径
- 第二行:项目使用的 go 版本
- 第三行:项目所需的直接依赖包及其版本
在实际应用上,你会看见更复杂的 go.mod 文件:
module github.com/BingmingWong/module-test go 1.14 require ( example.com/apple v0.1.2 example.com/banana v1.2.3 example.com/banana/v2 v2.3.4 example.com/pear // indirect example.com/strawberry // incompatible ) exclude example.com/banana v1.2.4 replace( golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac => github.com/golang/crypto v0.0.0-20180820150726-614d502a4dac golang.org/x/net v0.0.0-20180821023952-922f4815f713 => github.com/golang/net v0.0.0-20180826012351-8a410e7b638d golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0 )
- exclude: 忽略指定版本的依赖包
- replace:由于在国内访问golang.org/x的各个包都需要***,你可以在go.mod中使用replace替换成github上对应的库。
go.sum文件
每一行都是由 模块路径,模块版本,哈希检验值 组成,其中哈希检验值是用来保证当前缓存的模块不会被篡改。hash 是以h1:开头的字符串,表示生成checksum的算法是第一版的hash算法(sha256)
go mod命令的使用
- go mod init:初始化go mod, 生成go.mod文件,后可接参数指定 module 名。
- go mod download:手动触发下载依赖包到本地cache(默认为$GOPATH/pkg/mod目录)
- go mod graph: 打印项目的模块依赖结构
go mod tidy :添加缺少的包,且删除无用的包
go mod verify :校验模块是否被篡改过
go mod why: 查看为什么需要依赖
go mod vendor :导出项目所有依赖到vendor下
go mod edit :编辑go.mod文件,接 -fmt 参数格式化 go.mod 文件,接 -require=golang.org/x/text 添加依赖,接 -droprequire=golang.org/x/text 删除依赖,详情可以参考 go help mod edit
go list -m -json all:以 json 的方式打印依赖详情
给项目添加依赖
- 你只要在项目中有 import,然后 go build 就会 go module 就会自动下载并添加。
- 自己手工使用 go get 下载安装后,会自动写入 go.mod
GOPATH 做为 Golang 的第一个包管理模式,只能保证你能用,但不保证好用,而 go vendor 解决了 GOPATH 忽视包版本的管理,保证好用,但是还不够好用,直到 go mod 的推出后,才使 Golang 包的依赖管理有了一个能让 Gopher 都统一比较满意的方案,达到了能用且好用的标准
3.2 编码规范
项目结构
- templates (views) # 模板文件
- public (static) # 静态文件
- css
- fonts
- img
- js
- routers (controllers) # 路由逻辑处理
- models # 数据逻辑层
- modules # 子模块
- setting # 应用配置存取
- cmd # 命令行程序命令
- conf # 默认配置
- locale # i18n 本地化文件
- custom # 自定义配置
- data # 应用生成数据文件
- log # 应用生成日志文件
命令行应用
当应用类型为命令行应用时,需要将命令相关文件存放于 /cmd 目录下,并为每个命令创建一个单独的源文件:
/cmd dump.go fix.go serve.go update.go web.go
导入标准库、第三方或其它包
除标准库外,Go 语言的导入路径基本上依赖代码托管平台上的 URL 路径,因此一个源文件需要导入的包有 4 种分类:
- 标准库
- 第三方包
- 组织内其它包
- 当前包的子包
基本规则:
- 如果同时存在 2 种及以上,则需要使用分区来导入。每个分类使用一个分区,标准库排最前面,第三方包次之、项目内的其它包和当前包的子包排最后,采用空行作为分区之间的分割。
- 在非测试文件(*_test.go)中,禁止使用 . 来简化导入包的对象调用。
- 禁止使用相对路径导入(./subpackage),所有导入路径必须符合 go get 标准。
注释
规范:
- 所有导出对象都需要注释说明其用途;非导出对象根据情况进行注释。
- 如果对象可数且无明确指定数量的情况下,一律使用单数形式和一般进行时描述;否则使用复数形式。
- 包、函数、方法和类型的注释说明都是一个完整的句子。
- 句子类型的注释首字母均需大写;短语类型的注释首字母需小写。
- 注释的单行长度不能超过 80 个字符。
包注释
位于 package 之前,如果一个包有多个文件,只需要在一个文件中编写即可
如果你想在每个文件中的头部加上注释,需要在版权注释和 Package前面加一个空行,否则版权注释会作为Package的注释。
如果是特别复杂的包,可单独创建 doc.go 文件说明
struct、interface以及其他类型
特别注释
TODO:提醒维护人员此部分代码待完成
FIXME:提醒维护人员此处有BUG待修复
NOTE:维护人员要关注的一些问题说明
命名规则
文件名
整个应用或包的主入口文件应当是 main.go 或与应用名称简写相同。例如:Gogs 的主入口文件名为 gogs.go。
函数或方法
若函数或方法为判断类型(返回值主要为 bool 类型),则名称应以 Has, Is, Can 或 Allow 等判断性动词开头:
func HasPrefix(name string, prefixes []string) bool { ... } func IsEntry(name string, entries []string) bool { ... } func CanManage(name string) bool { ... } func AllowGitHook() bool { ... }
常量
变量
常见特有名词:
// A GonicMapper that contains a list of common initialisms taken from golang/lint var LintGonicMapper = GonicMapper{ "API": true, "ASCII": true, "CPU": true, "CSS": true, "DNS": true, "EOF": true, "GUID": true, "HTML": true, "HTTP": true, "HTTPS": true, "ID": true, "IP": true, "JSON": true, "LHS": true, "QPS": true, "RAM": true, "RHS": true, "RPC": true, "SLA": true, "SMTP": true, "SSH": true, "TLS": true, "TTL": true, "UI": true, "UID": true, "UUID": true, "URI": true, "URL": true, "UTF8": true, "VM": true, "XML": true, "XSRF": true, "XSS": true, }
声明语句
3.3 命令大全
基本命令
// 查看版本 $ go version // 查看环境变量 $ go env //设置环境变量 $ go env -w GOPATH=/usr/loca
程序执行
Go语言的程序必须先编译再执行
1、先使用go build编译成二进制文件,再执行这个二进制文件
2、使用 go run “直接”运行,这个命令还是会去编译,但是不会在当前目录下生成二进制文件,而是编译成临时文件后直接运行
编译文件
将 .go 文件编译成可执行文件,可以使用 go build
helloworld 文件夹下,包含两个 .go 文件,它们都归属于同一个包
当使用 go build 可指定包里所有的文件,默认会以第一个文件(main.go)名生成可执行文件(main)
如果不指定,则生成的可执行文件是以文件夹命名;除此之外,也可以手动指定可执行文件名
清除编译文件
使用 go install 有可能会生成很多的文件,如可执行文件,归档文件等,它们的后缀名非常多,有 .exe, .a, .test,.o,.so,.5 ,.6,.8,通过使用go clean可以进行一键清理
实际开发中go clean命令使用的可能不是很多,一般都是利用go clean命令清除编译文件,然后再将源码递交到 github 上,方便对于源码的管理,go clean中的参数有:
下载代码包
在 Golang 中,除了可以从官方网站(golang.org)下载包之外,还可以从一些代码仓库中下载,诸如 github.com,bitbucket.org 这样的代码托管网站。
go get ,可以借助代码管理工具通过远程拉取或更新代码包及其依赖包,并自动完成编译和安装
这个命令可以动态获取远程代码包,目前支持的有 BitBucket、GitHub、Google Code 和 Launchpad。在使用 go get 命令前,需要安装与远程包匹配的代码管理工具,如 Git、SVN等;go get 会根据域名的不同,使用不同的工具去拉取代码包
下载和安装,原本是两个动作,但使用 go get 后,它默认会将下载(源码包)和安装(go install)合并起来,当然你也可以通过参数指定拆散它们。
go get 命令的参数:
go get [-d] [-f] [-t] [-u] [-v] [-fix] [-insecure] [build flags] [packages]
注意
- 当指定-u时,不管需不需要更新,都会触发重新下载强制更新
- golang中包的路径
- 对于下载的包来说,如何指定版本进行下载:
# 拉取最新 go get github.com/foo # 最新的次要版本或者修订版本(x.y.z, z是修订版本号, y是次要版本号) go get -u github.com/foo # 升级到最新的修订版本 go get -u=patch github.com/foo # 指定版本,若存在tag,则代行使用 go get github.com/foo@v1.2.3 # 指定分支 go get github.com/foo@master # 指定git提交的hash值 go get github.com/foo@e3702bed2
安装代码包
使用go install时,如果安装的是一个可执行文件(包名是 main),它会生成可执行文件到 bin 目录下。这点和 go build 很相似,不同的是,go build 编译生成的可执行文件放在当前目录,而 go install 会将可执行文件统一放至 $GOPATH/bin 目录下;如果你安装的是一个库,它会将这个库安装到 pkg 目录下,生成 .a 为后缀的文件
3.4 包导入
在 Go 语言中,一个包可包含多个 .go 文件(这些文件必须在同一级文件夹中),这些 .go 文件的头部都使用 package 关键字声明了同一个包
主要有两种方式进行包导入:
- 单行导入
import "fmt" import "sync"
- 多行导入
import( "fmt" "sync" )
使用别名
导入了两个具有同一包名的包时产生冲突,此时这里为其中一个包定义别名
import ( "crypto/rand" mrand "math/rand" // 将名称替换为mrand避免冲突 )
导入了一个名字很长的包,为了避免后面都写这么长串的包名,可以这样定义别名
import hw "helloworldtestmodule"
防止导入的包名和本地的变量发生冲突,比如 path 这个很常用的变量名和导入的标准包冲突
import pathpkg "path"
使用点(.)操作
包的初始化
包的匿名导入
import 导入的是目录(路径)
相对导入和绝对导入
使用相对导入,注意事项:
- 项目不要放在 $GOPATH/src 下,否则会报错
- Go Modules 不支持相对导入,在你开启 GO111MODULE 后,无法使用相对导入
- 使用相对导入的方式,项目可读性会大打折扣,不利用开发者理清整个引用关系。
一般更推荐使用绝对导入的方式,但是绝对导入会涉及到优先级:
包导入的优先级
根据三种不同的包依赖管理方案,不同的管理模式,存放包的路径可能都不一样,有的可以将包放在 GOPATH 下,有的可以将包放在 vendor 下,还有些包是内置包放在 GOROOT 下。
那么问题就来了,如果在这三个不同的路径下,有一个相同包名但是版本不同的包,我们导入的时候,是选择哪个进行导入呢
如果使用 govendor
当我们导入一个包时,它会:
- 先从项目根目录的 vendor 目录中查找
- 然后从 $GOROOT/src 目录下查找
- 最后从 $GOPATH/src 目录下查找
- 都找不到的话,就报错。
如果使用 go modules
- 你导入的包如果有域名,都会先在 $GOPATH/pkg/mod 下查找,找不到就连网去该网站上寻找,找不到或者找到的不是一个包,则报错。
- 如果你导入的包没有域名(比如 “fmt”这种),就只会到 $GOROOT 里查找。
- 还有一点很重要,当你的项目下有 vendor 目录时,不管你的包有没有域名,都只会在 vendor 目录中查找。
通常vendor 目录是通过 go mod vendor 命令生成的,这个命令会将项目依赖全部打包到你的项目目录下的 verdor 文件夹中。