写在前面,
写这篇文章的初衷,是想向初学者解释为什么需要设 $GOPATH,且为什么要把代码放到 $GOPATH 下面。另一方面,最近 go 1.11 发布,新推出包管理工具 go modules 弃用了 $GOPATH,这里也是替 go modules 站站队,说明了用 $GOPATH 的痛点。
这天,作为优秀程序员的你,决定学习一下莫名火起来的 golang,你很厉害,花了一个小时便安装好了开发环境,配置好了 $GOPATH、$GOROOT 之类的环境变量。
一切还是要从 Hello World 开始,你迅速学习了一下 golang 的基本语法,写了一个 Hello World 代码放在桌面的 hello-world/main.go
里,执行 go build main.go
,成功编译,成功执行,你很开心。
小有成就的你,决定写一个项目练练手,你新建了一个 golang 的源文件放在桌面的 tiny-project/main.go
。
这个项目有需要用到 github.com/xxx/yyy
这个三方包,这难不倒你,你执行 go get github.com/xxx/yyy
,成功安装了这个包。你知道,这次安装其实就是把 https://github.com/xxx/yyy
上存放的代码下载到 $GOPATH 下面,编译器会去 $GOPATH 找所需要的包的代码。
这个小项目是迭代式开发的,代码越来越多,优秀的你知道不应该把所有代码都挤在一个 main.go
文件里,于是你是拆分成了多个文件:
main.go
logic.go
dao.go
entity.go
但是在编译时,你执行 go build main.go
是发现报错了,报告很多程序实体的定义没有找到,你搜索了一下,找到一个方案解决了这个问题,即执行 go build main.go logic.go dao.go entity.go
,但是作为一个优秀的程序员,你敏锐的感觉到这样做是不优雅的。于是你进一步发掘了一下这个问题,明白了过来,你可以单纯执行 go build
就可以编译一个包,不需要指定额外的参数。
这时候你领悟了过来,原来对于 golang 来说,你写的这个 tiny-project
也应该是一个包,与 golang 的其他三方包没有什么太大的区别,所以要想规规矩矩成为一个包,你的这个小项目也应该按照 golang 包的命名规则,放到指定的文件路径下。
你思量了一下,把你的 tiny-project
放到了 $GOPATH/src/tiny-project.com/tiny-project-lib/tiny-project/
,完成后你撇撇嘴嘟哝了几句,原来,包名中的 tiny-project.com
是你随便写的,你并没有这个域名的所有权,你也没有把代码放到 http://tiny-project.com
这个网站上,tiny-project
的代码你一直是托管在你自己私有的一个 SVN 上,并不打算公开,但是现在,你不得不假装它公开在 http://tiny-project.com/tiny-project-lib/tiny-project
。
虽然有些怪怪的,但是好在你在终于能方便地编译了,只需要在 $GOPATH/src/tiny-project.com/tiny-project-lib/tiny-project/
下执行 go build
即可,方便多了。
项目继续开发,文件阅读越多,你意识到应该开一个子文件夹,于是,项目结构变成了:
main.go
logic.go
dao.go
entity/
user.go
product.go
transaction.go
这时候你发现,原来对于 golang 来说,新建了一个文件夹就意味着新建了一个新包,这个包是tiny-project.com/tiny-project-lib/tiny-project/entity
,从此,你项目中的 dao.go
想引用的 entity
下定义的实体时,虽然文件路径近在咫尺,却需要写:
import tiny-project.com/tiny-project-lib/tiny-project/entity
你觉得有一点啰嗦,想换成 import ./entity
,你知道这样是可行的,但这时候蹦出来一个资深 golang 程序员,阅码无数,他告诉你,几乎没有人会在 golang 中使用相对引用。所以,你不想成为异类。
终于有一天,你的小项目瓜熟蒂落,变得相当复杂但也相当有用,你的朋友希望你开源,于是你决定把项目移动到 GitHub 上: gihub.com/tiny-project-lib/tiny-project
,问题来了,你意识到项目的移动也意味着包的重命名,而包重命名意味着代码的文件路径需要更改,而更难受的是,竟然还需要修改代码,引用 entity 的代码需要改为:
import gihub.com/tiny-project-lib/tiny-project/entity
至此你发现,
$GOPATH 指向的路径,决定了代码的文件路径;
代码的文件路径,决定了包名;
包名,决定了包在互联网上的存放位置;
包名,也决定了代码里怎么写 import。
以上任何一个环节的改动,就会牵扯出其他环节的一系列改动。
这便是 $GOPATH 耦合之殇。
写在后面,
事实上,文中提到一些“必须修改”,在技术上确实可以做到一些规避的,比如 $GOPATH 可以指定多个文件夹、文件夹名不一定要和包名一致、
go get a.com/xxx/yyy
也可以是从 GitHub 上下载源码,但是文中所说的“耦合”,更多时候是基于约定上的耦合,即:它在具体实现时不一定非要是这个样子,但在约定上,它应该是这个样子。所以读者非要较真,说这些耦合可以通过怎样怎样解除,我是没有异议的。但是,改一个具体的项目容易,改整个生态系统的约定难,尤其,不是所有的约定都是应该被粗暴打破的,可能通过升级、补充来加以完善才是让大家满意的做法。
期待 go modules 打破僵局。
评论加载中……
若长时间无法加载,请刷新页面重试,或直接访问。