Golang package 机制给我的感觉像 Java/C++/Python 杂糅的产物. 也许是我习惯了以单个文件, 或者说类为基本模块的代码组织方式, 对于Golang, 还没有找到一个正确的”姿势”去组织代码吧.
下面就开启”吐槽”模式.
package名难取, 容易和变量名”撞车”来看下面这个简单的例子
所以一方面, package 在取名的时候, 需要想办法避开那些很可能作为变量名的名称.
bufiobuffhashsql
Java怎么做的?
Java 里面, 代码是严格按照类来组织的. 不同于 Golang 一次将整个 package 引入, Java 是对每个类单独引入. 按照 Java 习惯, 类名首字母大写, 变量名首字母小写, 故而从名字上就能很好地区分类调用和对象调用. 就算你”不走寻常路”, 要变量名和包名撞车, 那也不会有什么危险, 因为都一定是调用同一个静态方法.
如何避免
也许Golang编译的时候对这种撞车检查, 拒绝通过.
或者参考C++的namespace语法, 将 package 方法调用和对象方法调用分开:
回到现实, 以上两点只是妄想, 语法规则一旦订立, 就没法再改了. 连govet都拒绝侦测这种情况, 所以还是得靠自己注意.
如何实现”静态方法”
可以通过构造一个”占位”的类型来实现类似Java的纯静态类:
这种写法适用于实现非常类似, 没必要拆分到不同package的场景.
要我短? 臣妾做不到Golang 鼓励命名简短, 但是简短命名带来的时更高的命名冲突几率. 很多时候不得不退而求其次.
package 重名
ks.writerrq.writera::b::c
YY一下, 要是支持使用相对/绝对 package 路径的引用, 那也不错:
为了避免自己写的 package 被 import alias (打脸么这不是), 实际点的做法:
ks.writerks.kswriterkswksks.writerks.readerks
同一个 package 下的命名困惑
如果为了package 名简短, 那么对象名, package 的方法名, 就简短不了:
而且创建对象的方法名也需要修改
所以说, 取简短有力的名太难.
side effect import 带来膨胀Golang package 是最基本的 import 单位, 因此会导致不必要的额外依赖. 还是上面的例子:
reader.go
也许这就是静态链接所带来的代价? 但人家C++就可以做到没用的代码就不编译.
所以如何组织好 package 的结构, 减少不必要的依赖引入, 也是个比较麻烦的事情.
静态编译 vs 依赖注入 (DI)静态编译好处是非常明显的, 但有时也导致了一些不灵活. 比如说在做依赖注入的时候, 就比较难办. 没法在不改代码的情况下替换一部分组件. 最好的办法, 是在入口文件的地方做依赖注入.
go generate
main package 粘性问题
main package 意味着什么? 代码黑洞:
- 只可以它复用其他库, 而不能被其他库复用;
- 太容易通过全局变量交互, 导致代码越来越”黏”;
- 没法导出多个可执行程序, 除非在程序内部做方法路由
所以项目到了一定规模, 必然要对 main package 作拆分.
public void static main()
可见性问题
Golang 用大写开头决定可见性, 只有public/private语义. 和C++/Java相比, 缺少了protected概念, 这在绝大部分情况下是OK的.
但是当我们一个内部实现越来越复杂, 需要拆分成几个内部模块. 由于没有机制保护其可见性, 就会被外部 package 调用, 对重构造成不必要的麻烦.
为了解决这个问题, Golang 1.4 引入了 internal package. internal是protected属性, 仅对当前及更深层级的目录可见.
依赖问题go get
- 网络永远联通
- 每个 repo 网站永远不掉链子
- 每个 package 永远都只有一个版本
于是就有了 gopkg.in, 提供API兼容的版本管理.
此外, 有类似govendor等第三方依赖管理程序, 将依赖库的精确版本(commit hash)记录下来. 从而可以通过依赖库的 commit hash 校验来做到确保 “可重现” 的构建.
vendor
但是还存在一个依赖冲突的问题, 比如:
如何构建C?
为了避免了传递性依赖所带来的潜在依赖冲突, 只能要求每个项目”自举”, 也就是每个项目, 统统没有外部依赖, 自然也就木有冲突了.
Golang 1.6 之后正式采用了 vendor 的方式来自举. 即把所有的依赖拷贝到 package 内部, :
当然这带来的弊端就是依赖代码的膨胀. 试想一下, 如果项目所依赖N个库都用vendor方法依赖了一个通用库, 那么这个通用库在vendor目录下就出现了N次! 因此, 会随着vendor使用的逐步采用, 指数膨胀下去.
govendor 的思路是将相同的 package 全部拍平.
此外, 如果被重复依赖的库不是静态的纯函数调用, 而是带状态的服务, 那么如何决断, 也是悬而未决的事情…
Reference