一文带你了解Go的工程管理

大家好,我是asong,这是我的第三篇原创文章。这一篇将对Go的工程管理进行详细的介绍,学习了这些,对我们的日常项目开发有很大帮助。最后对go module的使用进行详细介绍(一定要看到最后呦!)。

一、golang中包的概念

Go语言中使用包来组织源代码的,并且通过命名空间进行管理。所以所有源代码都必须属于某个包。每个go文件中第一行都必须是以package name声明自己所在包。

在介绍包之前,我们需要了解一下Go作用域问题。Go语言的作用域有一点区别于其他语言,所以一定好好掌握这一点,要不就会出现令人头皮发麻的bug,浪费我们开发的时间。

Go语言有三种类型的作用域:

  • 全局作用域:所谓全局作用域,就是在任何地方都可以访问的标识符。可以分为两类,一类是Go语言内置的预声明标识,比如类型、关键字等,我们在任何文件中都可以直接使用;另外一类,Go语言包内以大写字母开头的标识符,比如变量、常量、函数等,只要它的第一个字母是大写的,它们就具有全局作用域,我们在任意文件中就可以看到;这是Go语言设计者特别设计的,在其他语言中是没有这个,较新颖,需要注意。
  • 包内作用域:既然以大写字母开头的标识符是全局作用域,那么以小写字母开头的标识符,就是仅在本包可见,其他包是不可见的,也就是具有包内作用域。
  • 局部作用域:局部作用域也就是每个代码块内定义变量统称为局部变量,这些局部变量仅在当前代码块内可见。

Go语言中包是可以定义在很深的目录中,包的定义是不包括目录路径的,但是包的引用一般都是全局路径引用。标准包源码是位于GROOT/src下面,可以直接引用,但是自定义包必须放到GOPATH/src目录下才可以被引用。包引用这里就不做特别说明了,没有什么特别的,正常引用即可。这里主要介绍一下包引用格式,这里有一些特殊。包引用主要有四种引用方式。这里以fmt包为例子,进行代码示范。

  • 普通引用:也就是标准引用,代码示范如下:
import "fmt"
  • 别名引用:也就是我们在引用包的时候可以给这个包起一个别名,这样当包名比较长或者难记的时候,可以使用这个别名进行函数调用。代码示范如下:
import M "fmt"
  • 省略包名方式:我们在对包中方法进行调用时,需要加上包名.进行调用,如果我们在包引用时使用一个点代替,那么在对包中的方法进行调用时,不需要再写包名.,直接调用即可。代码示范:
import . "fmt"func main(){    //不再需要fmt.    Println("test")}
  • 只想执行初始化init函数:有时候我们导入包只是为了执行初始化init函数,不需要使用包中的其他函数方法,那么我们在包引用的时候,使用如下方式,即可实现
import _ "fmt"

这里提到了init函数,init函数是在什么时候加载运行的呢?下面介绍一下包的加载过程。

Go语言的程序在执行main/main之前,Go会对整个程序的包进行初始化,执行流程如下:

  • 包初始化程序从main函数引用的包开始,逐级查找包的引用,直到找到没有引用其他包的包,最终生成一个包引用的有向无环图。
  • Go编译器会将有向无环图转换为一棵树,然后从树的叶子节点开始逐层向上对包进行初始化。
  • 单个包的初始化过程,先初始化常量、然后是全局变量,最后执行init函数。

二、Go语言包管理

包管理这里一直是Go诟病的一个功能。在Go1.11版本之前,Go使用go get获取第三方工具包,这种方式缺乏对依赖包版本的管理和可复制构建的支持。所以Go社区中已有一些解决方案,比如dep、glide等。但是依然会出现一些问题,比如依赖包版本的变化、依赖包的本地缓存问题、GOPATH的问题。所以Go在1.11版本引出了go module来解决这些问题,这一节我将对go module前后的一些变化进行介绍。

go get默认情况下是不支持包版本控制。在Go1.11之前的版本中,添加依赖意味着将该依赖项的源代码仓库克隆到GoPath下面。所以没有版本的概念。所以当不同的项目需要依赖包的不同版本时,Go包管理工具无法实现。

所以在Go1.5引入了vendor机制。使用vendor需要手动设置环境变量GO15VENDOREXPERIMENT=1,在Go1.6以后,默认是开启vendor目录查找的。vendor机制其实就是在包中引入vendor目录,将依赖部分的外部包复制到vendor目录下,这样编译器在查找外部依赖包时,就会优先在vendor目录下进行查找,所以使用vendor机制后,查找第三包流程如下:

  • 如果当前包下有vendor目录,则从其下查找第三方的包,如果没有找到,则继续执行下一步操作。
  • 如果当前包目录下没有vendor目录,则沿当前包目录向上逐级目录查找vendor目录,直到找到GOPATH/src下的vendor目录,只要找到vendor目录就去其下查找第三方的包,如果没有则继续执行下一步操作。
  • 在GOPATH下面查找依赖包
  • 在GOROOT目录下查找依赖包。

vendor它为工程独立的管理自己所以依赖第三包提供了保证,多个工程独立管理自己的第三方依赖包。

但是vendor有一个重要的问题没有解决,那就是没有对外部依赖的第三方包的版本管理。我们通常是使用go get -u更新第三方包。默认的是将工程的默认分支的最新版本拉取到本地,并不能指定第三方包的版本。在实际的升级过程中,如果发现新版本有问题,则不能很快回退,这是个问题。所以Go官方的包依赖管理工具dep、go module都是为了解决这个问题。dep笔者也没有使用过,暂时不做介绍了,本文主要讲解go module。

go module带来了三个重要的内置功能:

  • go.mod文件,他与package.json文件功能类似
  • 机器生成的传递依赖项描述文件 go.sum
  • 不要需要GOPATH限制,模块可处于任何路径下。

go mod在Go1.11和1.12版本中是需要手动开启,在1.12以后默认开启go mod。为了更好的讲解go module的使用,我们创建一个例子来完成说明。开发工具是Goland。Go版本:1.14。

由于GOPATH不再必要的了,我们可以在GOPATH外创建项目。为了方面我在桌面创建一个文件,具体文件格式如下:

image

之后我们使用Goland打开该目录,进行一些配置。我们进入settings界面,在Go模块下,配置Go moudules,配置如下即可:

image

这就配置好了,注意这里不需要配置GOPATH,如果配置了会导致错误出现。配置好后,我们打开终端,使用go mod init (项目目录) 进行初始化,这样该项目目录下会生成go.mod文件,执行go build会生成go.sum文件。好了这就使用上了go module了。那么我就来搞点事情,看一看go.mod是怎么工作的吧。我们在main.go文件中,添加beego依赖,代码如下:

package main

import (
    "github.com/astaxie/beego"
)
func main()  {
    beego.Run()
}

如果是以前,需要先执行go get去获取包,才能使用,现在有了包管理,就不需要了。我们直接go build main.go就可以了。这是我们就会看到控制台输出如下:

image

再看go.mod文件如下:

module src

go 1.14

require (
    github.com/astaxie/beego v1.12.1
    github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
)

我们可以看出来新增了require模块,这里是关键引用,最后的v1.12.1就是版本号。在使用模块时,go命令将完全忽略vendor目录。为了向后兼容旧版本Go,获确保用于构建的所有文件以起存储在单个文件树中,我们可以运行go mod vendor。这样在主模块的根目录下生成一个vendor目录,并将依赖模块中的所有软件包存储在该目录中。运行执行可以看到vendor目录如下:

image

这就是一个简单go模块样例,已将样例代码打包上发github上,有需要的可以下载观看。下载地址如下:

Go模块代码样例,点击此处:https://github.com/sunsong2020/Golang_Dream/tree/master/code_demo/module_demo

好了,今日的分享结束了。结尾在此推广一下我的公众号:Golang梦工厂,我会不断发表关于Golang方面的知识,面试,个人理解等多个方面,一定对你受益匪浅。公众号搜索:Golang梦工厂,或直接扫描下方二维码即可。

image

发表评论

您的电子邮箱地址不会被公开。