Go 中代码的组织管理通过仓库,模块及包三个概念来完成,其中

  • 仓库(repository)就是大伙熟知的代码仓库
  • 模块(module)是库(library)或程序(app)的根节点,存放于仓库,可包含多个包
  • 包(package)同一目录下的源码文件,编译后会归到一起
com.companyname.prjectname.librarygithub.com/wayou/hello

go.mod

go mod init 
$ go mod init github.com/wayou/hello
go: creating new go.mod: module github.com/wayou/hello
go: to add module requirements and sums:
        go mod tidy
go.mod
module github.com/wayou/hello

go 1.16

该文件保存了项目中使用的 Go 版本,模块名,如果有安装三方依赖,也会存放在这里进行管理。

go get 
$ go get github.com/subosito/gotenv
go.mod
module github.com/wayou/hello

go 1.16

require (
        github.com/lib/pq v1.10.2 // indirect
        github.com/subosito/gotenv v1.2.0 // indirect
)
replaceexclude

导入导出

包中使用大写字母开头的名称(变量,方法,常量,类型及结构体中的字段)会被导出,对应地,小写或下划线开头的名称只能包内使用。

首先创建一个文件夹,存放需要被调用的模块

$ mkdir greetings && cd $_
$ go mod init github.com/wayou/grettings

# 再创建源码文件
$ touch greetings.go

其中源码内容中包含一个导出的方法,作用是将输入的字符串进行格式化后打印:

// grettings/grettings.go

package greetings

import "fmt"

// Hello returns a greeting for the named person.
func Hello(name string) string {
    // Return a greeting that embeds the name in a message.
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message
}

创建另外个文件夹,在其中调用上面的模块:

$ mkdir hello && cd $_
$ go mod init github.com/wayou/hello

# 创建源码文件
$ touch hello.go

其中导入并调用的逻辑为:

package main

import (
    "fmt"

    "github.com/wayou/greetings"
)

func main() {
    // Get a greeting message and print it.
    message := greetings.Hello("Gladys")
    fmt.Println(message)
}
-replace
$ go mod edit -replace github.com/wayou/greetings=../greetings
go mod tidy
$ go mod tidy
go: found github.com/wayou/greetings in github.com/wayou/greetings v0.0.0-00010101000000-000000000000
github.com/wayou/hellogo.mod
module github.com/wayou/hello

go 1.16

replace github.com/wayou/greetings => ../greetings

require github.com/wayou/greetings v0.0.0-00010101000000-000000000000

至此模块就能被正确调用到了,

$ go run .
Hi, Gladys. Welcome!
Enable Go moduels integration


包名冲突的解决

import
math/randcrypto/rand
import (
	crand "crypto/rand"
	"math/rand"
)

这一处理也适用于引入的包里包含与本地已经存在的名称冲突的情形。

godoc

文档注释没有特别的语法,就是普通注释。

doc.gofmt

其他名称(identifier)的注释,默认要以该名称开头,空格后跟对应的注释。比如下面的结构体及方法:

// Money represents the combination of an amount of money
// and the currency the money is in.
type Money struct {
	Value decimal.Deciaml
	Currency string
}

// Convert converts the value of one currency to another.
//
// Supported currencies are:
// 		USD - US Dollar
// 		EUR - Euro
//
// some other description goes here...
//
// some more description...
//
// See more on https://www.example.com/xxx/yyy
func Convert(from Money, to string) (Money, error){
	// ...
}

编辑器中的样子:

注意如果要展示时换行,使用一行空格来表示,上面方法最终展示的效果如下图:


go doc go doc 

内部包

internalinternal

譬如有如下的包结构:

.
├── bar
│   └── bar.go
├── example.go
├── foo
│   ├── foo.go
│   ├── internal
│   │   └── internal.go
│   └── sibling
│       └── sibling.go
└── go.mod
internalsiblingfooexample.gobar
init
init
init

但新的实践中已不推荐这么做,目前的 Go 实现中之所以还保留只是考虑到向后兼容。推荐的做法是显式地去注册和调用你的模块。

外部包的安装

go get go mod tidygo.modgo.sum

默认会安装模块的最新版本,可在依赖中指定具体的版本。

go list
$ go list -m -versions github.com/shopspring/decimal
github.com/shopspring/decimal v1.0.0 v1.0.1 v1.1.0 v1.2.0

其中:

-mgo list-versions

安装指定版本:

$ go get github.com/shopspring/decimal@v1.0.0

版本管理

当项目中多个模块同时依赖一个模块,且依赖的版本还不同,比如,

项目中有如下 A,B,C 三个模块,分别依赖模块 D 的三个版本:

  • A → D v1.0.0
  • B → D v1.1.0
  • C → D v1.2.0

Go 的原则是只会引入一个模块 D,选择的版本是最新的 v1.2.0,这就是最小引入原则,这和 npm 不一样,后者会同时引入所有依赖的版本,增加了包大小也引入了一些包管理上的复杂度。

但因为 A,B 依赖的并不是 v1.2.0 版本,编译时出错如何解决。因为语义化版本要求小版本号需要保证向后兼容,所以讲道理,1.x 的版本之间需要互相兼容,要么是包作者去解决这个兼容问题,要么是 D 中降低版本来迁就。

升级依赖

兼容性升级

go get -u -u=path

升级到不兼容的版本

v1.0.0->v2.0.0vNN
import (
	"github.com/shopspring/decimal/v2"
)
@
$ go get github.com/shopspring/decimal/v2@2.1.0

Vendoring

go mode vendor
go mode venfor

模块的发布

Go 是通过仓库来进行模块管理的,发布模块与别人共享就非常简单了,只需要将代码发布到公共 Git 仓库比如 GitHub, GitLab 等即可。

go mod tidygo test ./...git tag v1.0.0git push origin v1.0.0GOPROXY=proxy.golang.org go list -m example.com/@v1.0.0

以上。