再谈go mod

在很早以前开始学习go的时候,我推荐给了一个朋友,我那朋友对我说:“go连个包管理都没有,到它有包管理的时候再来学”,确实,go的包管理一直受到众多人诟病的地方之一。

所以在开始的时候,出现了很多第三方针对go的包管理器,因为都不是官方的,受制于go语言本身的制约(go get等),这些包管理器都存在这个那个不足,很混乱,go vendor 和 govendor 不一样的,一个是官方的前版本,一个是第三方的补充版本,弄得很晕。

备注:查看go的环境变量,用 go env 命令。

直到go的1.11版本,官方才出了个正式的go mod(也就是go module),在使用go mod的时候,这个go环境变量非常重要:GO111MODULE,这个变量有3个取值:
on:表示所有go的工程一律采用go mod方式来管理包。
off:表示所有go的工程一律不采用go mod方式来管理包,如此还是用以前的方式。
auto:默认是这个,为空也是这个,那么假如工程中有go.mod这个文件,那么采用go mod方式,否则采用以前的方式。

在不采用 go mod 方式的时候,用 go get 下载包的时候,是下载到 $GOPATH/src 下面。
在采用go mod方式的时候,用 go get 下载包的时候,是下载到 $GOPATH/pkg/mod 下面。

我们知道,在用 go get 下载包的时候,特别慢,为了解决这个问题:
在不采用 go mod 方式的时候,用 go get 下载包,则需要自己解决代理的问题,很麻烦。
在采用go mod方式的时候,用 go get 下载包,则可以有众多的免费代理供我们选择(注意,凡是这样的代理,都是在go mod模式下面,传统的不行),我们常用 https://goproxy.cn/ 来进行代理,设置如下:
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

或者设置linux的环境变量也是可以的:
export GO111MODULE=on
export GOPROXY=https://goproxy.cn

在用 go mod 方式来管理包的时候,在工程中会遇到2个问题:
1,本地的包如何导入:这个问题大家在开发的时候肯定会遇到,在用传统模式的时候,根本不会有问题的,但是到了 go mod 下,就遇到本地的包没有办法导入,这篇文章写得太好了,大家可以去看:如何使用go module导入本地包
2,私有库的包如何导入:我们常常会用gitlab搭建我们的私有git库,搭建gitlab的时候,往往有一些不同于正规github的设定,比如用http协议没有用https协议,比如不支持ssh协议,如此在解决go mod的时候,网上的很多关于golang的gitlab支持文章都失效,我个人认为解决办法就是clone下来吧,放在$GOPATH/src下面吧,没有其他好的办法。

在vscode下搭建golang的开发环境

golang的下载、安装、设置在这篇文章中就不叙述了。

本文讲述的是在 centos7 下面的 vscode 的 golang 开发环境搭建。

vscode的安装

vscode的安装非常简单,最好的方式就是前往官网:https://code.visualstudio.com/,那是最新的最权威的。
这里做个摘抄:

rpm --import https://packages.microsoft.com/keys/microsoft.asc
sh -c 'echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\nenabled=1\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" > /etc/yum.repos.d/vscode.repo'
yum check-update
yum install code

vscode的启动

很简单,直接在console中敲入 code 即可。但是假如以root身份运行的话,需要执行:
code --user-data-dir

vscode的汉化

打开vscode后,按下 ctrl + shift + p,会打开一个输入框,输入 configure 即可,会出现一个 Configure Display Language的选项,点下它进行安装简体中文,重启vscode后,就能看到汉化了。

更改颜色主题

默认的vscode是黑色主题,我一直喜欢白色的主题,对于黑色的主题,对我来说,很累眼睛(有些人认为黑色不累眼睛)。那么在vscode更改颜色主题的步骤很简单:左下角有个齿轮状的按钮,点击后出现:颜色主题,点击后出现一个输入框,找到淡色的白色主题即可。

取消迷你地图

我到现在也没有感觉到迷你地图用来干什么的,取消吧,占地方。点击菜单的”查看”,去掉“显示迷你地图”前面的勾即可。

设置快捷键

点击左下角的齿轮状的按钮,点击键盘快捷方式,出现许多的键盘快捷方式,进行调整。
我进行更改的几个快捷键如下:

删除一行  ctrl + d
后退  alt + <-
前进  alt + ->

安装golang的插件

在 vscode 中,按下 ctrl + shift + p,会打开一个输入框,输入 install 即可,会出现一个 Extensions: Install Extensions的选项,点下它后,在左侧会出现插件商店,在插件商店的输入框中输入:go 即可找到 go 的官方插件,点击后找到 install 即可。
golang插件装好以后,随便打开一个golang的工程,这个时候,vscode会在右下角连续弹出很多窗口提示安装golang的组件,逐个点击安装即可。因为我平时就已经把这些组件通过go get的方式安装好了,所以弹出的组件安装很少,更不用说遇到墙的事情了。假如被强了,自己可以到其他网站查找相关安装方法。
总的来说,vscode下的golang安装组件可以说是傻瓜式的,非常简单。

设置编辑区字体大小

默认编辑区的字体太小,点击左下角的齿轮状的按钮,点击“设置”,找到“文本编辑器”–>“字体”,找到 Font Size,改为 18 即可。

设置golang的大纲显示

打开golang的工程后,左侧的上半部分是文件及文件夹,左侧的下半部分是包或者类的大纲。我常用的开发视图是左侧是文件及文件夹,右侧是类的大纲,这样的界面显示能快速总览全局,也方便打开文件和类。但是在vscode中是没有办法把左上部分和左下部分进行分离(分离的效果是左侧文件及文件夹,右侧是类的大纲)。于是我们可以安装go outliner这个组件来加强这个功能,不过go outliner也没有办法单独显示在右侧。但是go outliner比vscode自带的那个大纲要强大很多,你们装上后就能发现两者的不同。在扩展商店中,找到go outliner,安装好后,进行manage,务必要在Extend Default Explorer tab with additinal section containing Go symbols的前面打勾。如此能在最左侧的能出现go outliner,点击后,比较完美的大纲出现了。就是因为不能在右侧,所以需要进行切换比较麻烦。

查找方法的引用和实现

在面向对象的语言中,对于查找某个方法的引用和实现是非常实用的一个技能。golang也提供了这一功能,这个功能是有guru这个组件来实现的。这个组件可以说非常的牛,具体可以看文档Using Go Guru,假如被墙的话,那么可以到这里来看Go Guru(golang 代码导航工具) 的使用。具体用guru来查找方法的引用和实现,只需要在方法上点击右键,然后点击“查找引用”和“查找实现”即可。但是这里千万千万要注意,在vscode中的,右键点击方法,然后选择“查找实现”是找不到结果的,正确的方法应该是:在接口类上点击右键,然后“查找实现”,这样guru会给你找出实现这个接口的实现类,切记切记,我尝试了很多次才试出来的!

delve的配置

在debug显示变量的时候,有些字符串的长度比较长,delve显示的结果是”xxx … +xx more”,怎么样让delve显示完整的字符串呢?在 settings.json中进行相应设置。(by the way,如何打开 settings.json?点击左下角的齿轮状按钮,然后点击“设置”,随便找一个“在 settings.json中编辑”点击即可)。进入settings.json后,输入 go.delveConfig,vscode马上会弹出可以设置选项给你,找到后,直接回车马上就能显示所有的delveConfig的选项,找到 maxStringLen,设为 9999 即可,如下:

"go.delveConfig": {
        "dlvLoadConfig": {
            "followPointers": true,
            "maxVariableRecurse": 1,
            "maxStringLen": 99999,
            "maxArrayValues": 64,
            "maxStructFields": -1
        },
        "apiVersion": 2,
        "showGlobalVariables": true
    }

工程中launch.json设置

在工程中,当你按下F5进行debug的时候,会发现vscode默认是当前所选文件作为入口点开始进行debug,往往当前文件不是main的入口文件,所以就会报错,所以我们要修改 launch.json 这个配置。点击菜单,找到“调试”,点击“打开配置”,即可打开 launch.json 这个文件,找到 program 这个配置项,设置为 “program”: “${workspaceRoot}/your-main-file.go”,其中的 ${workspaceRoot} 指得是打开的工程所在的文件夹,这些变量大家可以参考 vscode Variables Reference

golang的编辑器选择

很早就开始接触golang这门语言了,之间也用了不少不同的IDE来开发golang,但是每一种IDE都有这种那种的问题。

先说一下现在的结果吧:我现在用 vscode 来作为 golang 的开发工具,vscode 是我目前为止用过的最好的 golang 开发工具。

sublime

sublime是个伟大的产品,可以说vscode百分百是参照它来做的,很多套路基本上一样。不说sublime别的,就说golang在sumlime上的插件,做得就不是特别好,在golang代码的追踪上存在一些问题。用sublime来写golang那是很久以前的事情了,可能现在有了改观。但是随着vscode出来,就算有改观,我也不会再去尝试了,因为sublime要钱。

goland

jetbrains出品,要钱的,只对于我来说,我是对jetbrains的系列产品都是比较反感的,我不习惯它的这种开发风格,不适应。

goclipse

是eclipse中的一个插件,凭借这eclipse这个平台,在goclipse中开发golang,带来了太多熟悉的感觉,上手非常快,但是goclipse已经不再维护了,所以新的go版本在goclipse下面会报错。还有很多的go的组件都出问题了。这个非常可惜。强烈建议有能力的、有时间的人继续做下去。

liteide

在用vscode之前,我用的一直是liteide,大而全,基本上都有,界面也还行、代码跟踪采用 guru 也不错。但是 liteide 有各种各样的莫名其妙的问题,特别严重的是在 debug 这个环节,很多module的代码没有办法跟,即便用 delve 也是如此。本来我以为golang的debug在有些包下面会出现这样的问题(跟不进去的问题),等到我用了vscode后,才发现在 vscode 下很容易的跟踪进去,分析变量的值。于是,我毅然抛弃了 liteide,投向了 vscode的怀抱。

vscode

在vscode下安装golang的环境特别特别的方便,可以说算是傻瓜式的吧。vscode的golang开发环境可以说到目前为止最为优秀了,基本上需要的一些功能都能找到。对于vscode的golang开发,见另一篇博文《在vscode下搭建golang的开发环境》

tags在golang编译中的作用

golang没有条件编译,但是可以在编译的时候用tags参数来实现条件编译的效果。

这个参数的官方解释如下:

-tags 'tag list'
    a list of build tags to consider satisfied during the build.
    For more information about build tags, see the description of
    build constraints in the documentation for the go/build package.

等于没有说,说让去看 go/build package中的build constraints的描述,这又是在哪里?

在这里:go/build package文档

这个tags参数用法,可以参考这两篇文章,tags参数因为本身很简单,然后这两篇文章也写得好:
golang项目中使用条件编译
How to use conditional compilation with the go build tool

比较郁闷,在搜索引擎中搜索:golang tags,搜出来的基本上是tags在struct中的使用.

ldflags在golang编译中的2个作用

golang在编译的时候,可以传入一些参数,其中有 -ldflags 参数,这个参数的官方解释如下:

-ldflags 'flag list'
    arguments to pass on each 5l, 6l, 8l, or 9l linker invocation.

意思大概是:这个参数将影响链接这个过程。

对于链接所涉及的一些相关概念,请参考这篇文章静态链接,动态链接,静态库,共享库这些概念的详解

第一个用法

ldflags用于链接过程,这个具体的用法,这篇文章写得非常到味,比我写得好,我就不再写了,大家可以到这里查看,也谈Go的可移植性

第二个用法

ldflags在编译golang的时候,可以传入一些值用来配置golang的应用。这个用法简单,可以查看 golang在编译时用ldflags设置变量的值

第二个用法目前大多数用于应用程序的版本信息,本人认为第二个用法在应用程序假如有配置文件的话,那就没有太多意义了,完全可以用配置文件来替代这个功能。但是假如只是一个纯净的应用程序,没有配置文件的话,这个用法相当有意思了。

golang的继承实现

go的继承叫做“匿名组合”,这个不等同于组合,也就是说:“匿名组合”是“匿名组合”,“组合”是“组合”,两个完全不一样。下面看一个最简单的组合,然后再比较后面讲到的“匿名组合”,会发现两者不同。

type A struct {
}
func (a *A) Test() {
}

type B struct {
    a A
}
func (b *B) Test() {
    a.Test()
}

var b = new(B)
b.Test()

从上面可以看到“组合”的特点,要调用A的Test()方法,则需要用B的某个方法给封装一下,有没有一种更便利的方法,不用在B中写一个包装方法来调用,能不能省略掉B中的Test()包装方法呢?且看后面。

go的“匿名组合”,有三种实现形式:

1,匿名组合struct

type Human struct {
    Name string
}
func (a *Human) Test1() {}
type Student struct {
    Human
    Age int
}
func (a *Student) Test2() {}

var a = new(Student)
a.Name = "zhangsan"
a.Age = 11
a.Test2()
a.Test1() == a.Human.Test1()  //这2种方式都可以

2,匿名组合指向struct的指针

type Hobby struct {
    Name string
}
func (a *Hobby) Test1() {}

type Student struct {
    *Hobby
    Age int
}

var h = &Hobby{"movie"}
var s = &Student{h,18}
s.Test1()

初看这种方式和第一种匿名组合struct没有什么区别,而且更麻烦了,在创建Student的时候,还要对其中的*Hobby进行赋值。但是细细一想,这种模式的优点很大,这种类型的“匿名组合”实际上是第一种的“匿名组合”stuct再加上”组合“这2种模式而成。

对于第一种”匿名组合“struct,往往用在派生类和父类有一定继承关系上,而对于第二种的”匿名组合“指向struct的指针来说,派生类中的组合对象可以和父类没有太大的关系或者没有任何关系,就是一个纯粹的”组合“模式,上面讲到了,在组合模式中要调用父类的方法,需要在派生类中再写一个包装方法,很是麻烦。刚好,用第二种的”匿名组合“指向struct的指针这种模式来说,非常好的解决了这个问题,非常完美。

上面的例子假如看得还不是特别明白的话,好好体会下面这个例子就非常清楚了,这个模式非常不错:

package main

import (
    "fmt"
    "log"
    "os"
)

type MyJob struct {
    Command string
    *log.Logger
}

func (job *MyJob) Start() {
    job.Println("job started!") // job.Logger.Println

    fmt.Println(job.Command)

    job.Println("job finished!") // job.Logger.Println
}

func main() {
    logFile, err := os.OpenFile("./job.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0)
    if err != nil {
        fmt.Println("%s", err.Error())
        return
    }
    defer logFile.Close()

    logger := log.New(logFile, "[info]", log.Ldate|log.Ltime|log.Llongfile)
    job := MyJob{"programming", logger}

    job.Start()
    job.Println("test finished!") // job.Logger.Println
}

因为 MyJob 内组合的*log.Logger和MyJob关系不是纯粹的继承,假如将 log.Logger 采用组合的方式,那么还需要对 MyJob 再定义一个 Println 或者一系列方法,而采用”匿名组合“指向struct指针的这种方法就有效的解决了这些问题,细细体会。

3,匿名组合接口

这个和第2种模式几乎一样,也需要在实例化派生类的时候,要对其中的匿名组合进行接口实现类的赋值,道理和第2种一样。