3.1 Go Modules的入门

3.1.1 Go Module最初版本--GOPATH

可以将其理解为工作目录:
图片说明

每个目录存放的文件都是不相同的:

  1. bin:存放编译以后生成的二进制可执行文件
  2. pkg:存放编译后生成的.a文件
  3. 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进行开发遇到的问题:

  1. 你无法在你的项目中,使用指定版本的包,因为不同版本的包的导入方法也都一样
  2. 其他人运行你的开发的程序时,无法保证他下载的包版本是你所期望的版本,当对方使用了其他版本,有可能导致程序无法正常运行
  3. 在本地,一个包只能保留一个版本,意味着你在本地开发的所有项目,都得用同一个版本的包,这几乎是不可能的。

3.1.2 go vendor模式

go vender解决了GOPATH方案下不同项目下无法使用多个版本库的问题。
解决的思路就是,在每个项目下都创建一个 vendor 目录,每个项目所需的依赖都只会下载到自vendor目录下,项目之间的依赖包互不影响。在编译时,v1.5 的 Go 在你设置了开启GO15VENDOREXPERIMENT=1 (注:这个变量在 v1.6 版本默认为1,但是在 v1.7 后,已去掉该环境变量,默认开启 vendor 特性,无需你手动设置)后,会提升 vendor 目录的依赖包搜索路径的优先级(相较于 GOPATH)

其搜索包的优先级顺序,由高到低是这样的:

  1. 当前包下的 vendor 目录
  2. 向上级目录查找,直到找到 src 下的 vendor 目录
  3. 在 GOROOT 目录下查找
  4. 在 GOPATH 下面查找依赖包

go vender存在的问题:

  1. 如果多个项目用到了同一个包的同一个版本,这个包会存在于该机器上的不同目录下,不仅对磁盘空间是一种浪费,而且没法对第三方包进行集中式的管理(分散在各个角落)。
  2. 并且如果要分享开源你的项目,你需要将你的所有的依赖包悉数上传,别人使用的时候,除了你的项目源码外,还有所有的依赖包全部下载下来,才能保证别人使用的时候,不会因为版本问题导致项目不能如你预期那样正常运行。

3.1.3 go mod模式

GO111MODULE 是一个开关,通过它可以开启或关闭 go mod 模式。

它有三个可选值:off、on、auto,默认值是auto

  1. GO111MODULE=off禁用模块支持,编译时会从GOPATH和vendor文件夹中查找包。
  2. GO111MODULE=on启用模块支持,编译时会忽略GOPATH和vendor文件夹,只根据 go.mod下载依赖。
  3. 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文件

  1. 第一行:模块的引用路径
  2. 第二行:项目使用的 go 版本
  3. 第三行:项目所需的直接依赖包及其版本

在实际应用上,你会看见更复杂的 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
)
  1. exclude: 忽略指定版本的依赖包
  2. replace:由于在国内访问golang.org/x的各个包都需要***,你可以在go.mod中使用replace替换成github上对应的库。

go.sum文件
图片说明
每一行都是由 模块路径,模块版本,哈希检验值 组成,其中哈希检验值是用来保证当前缓存的模块不会被篡改。hash 是以h1:开头的字符串,表示生成checksum的算法是第一版的hash算法(sha256)

图片说明

go mod命令的使用

  1. go mod init:初始化go mod, 生成go.mod文件,后可接参数指定 module 名。
  2. go mod download:手动触发下载依赖包到本地cache(默认为$GOPATH/pkg/mod目录)
  3. 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 种分类:

  1. 标准库
  2. 第三方包
  3. 组织内其它包
  4. 当前包的子包

基本规则:

  • 如果同时存在 2 种及以上,则需要使用分区来导入。每个分类使用一个分区,标准库排最前面,第三方包次之、项目内的其它包和当前包的子包排最后,采用空行作为分区之间的分割。
  • 在非测试文件(*_test.go)中,禁止使用 . 来简化导入包的对象调用。
  • 禁止使用相对路径导入(./subpackage),所有导入路径必须符合 go get 标准。

注释

规范:

  1. 所有导出对象都需要注释说明其用途;非导出对象根据情况进行注释。
  2. 如果对象可数且无明确指定数量的情况下,一律使用单数形式和一般进行时描述;否则使用复数形式。
  3. 包、函数、方法和类型的注释说明都是一个完整的句子。
  4. 句子类型的注释首字母均需大写;短语类型的注释首字母需小写。
  5. 注释的单行长度不能超过 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
当我们导入一个包时,它会:

  1. 先从项目根目录的 vendor 目录中查找
  2. 然后从 $GOROOT/src 目录下查找
  3. 最后从 $GOPATH/src 目录下查找
  4. 都找不到的话,就报错。
    图片说明

如果使用 go modules

  1. 你导入的包如果有域名,都会先在 $GOPATH/pkg/mod 下查找,找不到就连网去该网站上寻找,找不到或者找到的不是一个包,则报错。
  2. 如果你导入的包没有域名(比如 “fmt”这种),就只会到 $GOROOT 里查找。
  3. 还有一点很重要,当你的项目下有 vendor 目录时,不管你的包有没有域名,都只会在 vendor 目录中查找。
    图片说明
    通常vendor 目录是通过 go mod vendor 命令生成的,这个命令会将项目依赖全部打包到你的项目目录下的 verdor 文件夹中。