这些年来,由于 Go 缺少依赖管理,社区涌现了五花八门的依赖管理方案,比如 glide、govendor、godep 等。而 Go 自 1.11 起,内置了 modules 方案,总算是来了个一锤定音。

Go modules 方案的设计是非常好的,网上介绍 Go modules 机制的文章很多,我这里也不再赘述。工作中写的一些 Go 项目,我都已经用上了该方案来做依赖管理。之所以这么快的拥抱新方案,是认为有 google 的背景,即使是新鲜出炉的功能,稳定性应该是有保证的,即使有坑,趟趟也就过去了。

但是在这个过程中,发现的一些问题让我对 Go 软件工程质量的信心大打折扣。

举个例子,如果你的开发环境是 centos6.5, git 版本是比较旧的 v1.7.1,这个时候使用 Go modules 是会有问题的(不要问我为什么要在 centos6.5 上面编译 Go 程序,因为我个人习惯将开发环境保持与线上环境一致)。在 go get 或者 go build 会报一些五花八门的错误,比如:

build xxxx: cannot find module for path yyy

或者:

unknown revision xxxx

具体是哪一种错误,取决于不同的执行时机,比如你是对一个之前没有用过 Go modules 的项目从头执行了 go mod init xxxx 做初始化之后再执行 go get 或者 go build,还是对一个已经使用Go modules 的项目直接执行 go get 或者go build。

如果遇到这类问题,解决方法非常简单,升级一下 git 版本到比较新的版本即可。这里说起来很轻描淡写,但是实际排查过程却是很让人恼火,因为整个 go cmd 的代码实在是太乱了,代码质量不敢恭维,阅读体验很差。那么问题在什么地方呢,在于在确定版本依赖关系的过程中,go cmd 代码里调用了一些 git 指令,而这些指令在较老版本的 git 中还没有支持。这是一个多么正常的使用场景,可是 Go 从 1.11 到现在已经发布的 1.11.2,均没有解决这个报错信息与真实原因风马牛不相及的问题。

一个系统的构建对其他某些组件的版本有依赖,这在软件工程中是一个再常见不过的事情。最简单的处理方式是,要么在文档中写明依赖的版本信息,要么在构建过程中,如果发现某些组件版本太低,就抛出相关信息,然后退出。无论怎么做,都是为了达到一个目的:告诉使用者发生了什么事情,该怎么做。而 Go 1.11 到 Go 1.11.2 里是怎么做的呢,发现 git 的一些指令执行失败了,却并不做好异常处理,一直到另一块代码中不得不失败退出,才丢出一个没有任何参考意义的错误信息。

也许你会觉得我有点小题大做,毕竟是软件就会有 bug。但真正让我恼火的是,一些 Go 的开发者居然保有这样的想法:

反正开发者们一般是在 ubuntu 上面开发,上面的 git 版本一般足够新了,编译完之后把二进制再部署到 centos 等老的系统上去就行了。

这话当然不是我杜撰的,具体见: https://github.com/golang/go/issues/26746

连我这种软件行业的凡夫俗子,都知道遇到异常的合理处理方式是什么(例如我在 2013 年写的一篇文章:再不判断异常分支就剁手),google 里绝对意义上的高手竟然对此毫不在意,实在让人大跌眼镜。