构建 Go 项目最佳实践

实践心得,比较零散,不一定正确。


Go 中的 package 针对的抽象层面是 module,并不是单个实体(面向对象中的类)。我理解 Go 最小粒度的抽象体现在文件层面,而非 package。

如果将 package 作为做小的粒度容易带来的问题:

当两个 package 存在一些共同的抽象,这一层的抽象就很难表达。在公共部分加一个 utils,用来作为公共抽象,那么这个公共抽象会越来越重度, 实际上 utils 应该是放一些业务无关的工具。还有一种解决办法是在同级别 package 加一层 utils 用来做公共业务的抽象,这是可行的,但是比较丑陋。 因为原则上来讲同一个层级的 package 的职责应该是相同的,加一个工具 utils 不优雅。

解决此类问题的最佳实践是将放大 package 的抽象粒度,作为一个 module ,包含很多个相关联的实体,如果有业务抽象在内部新增一个文件抽象即可。 减少 package 级别的业务再此抽象。

以前我还不太理解很多开源项目的 go 文件是平铺开来的,很少加内部 package,经过实践看来,通过 go 文件来表达实体要比 package 要能省很多事情。

避免过度抽象,过度设计。


类型定义和函数定义分开,统一维护一份类型定义(大都是与存储相关的)。这里的类型一般指的是公共的类型,如果是一次性的类型定义,放哪里都无所谓的。

为什么要这么做呢?

针对函数层面的循环依赖一般是架构设计不合理,但是 A package 调用了 B package 中的 函数 ,B package 中使用了 A package 中的 类型 却是很常见的情况。 所以为了避免循环依赖,把所有的类型统一到一起,它不会依赖于任何外部的类型或者函数。

另外,在代码维护角度这么做也更易维护代码,否则到处都是 struct,引用起来乱七八糟,想复用也比较难。

再另外,工具类 package (比如 util,common) 可以依赖外部的类型定义,但不要依赖外部的函数。


上面的办法也不好,如果单纯把类型和函数定义分开,容易乱。

如果把所有的公共类型全部抽离到 types 中是简单的,但是类型一般会携带一些 method,也就是说会有一些业务逻辑在里面。 那这样的话,types 就不能算是「公共」部分了,而是与业务强相关的部分。

解决办法是再抽一级目录,比如说定义的 types 用来标识项目的公共类型;子模块中再加一个 types 目录, 用来表示该子模块相关的 type,业务逻辑放在上一层。

这样就可以相对完美的解决模块依赖问题了。

First created: 2020-05-13 17:17:51
Last updated: 2022-12-11 Sun 12:49
Power by Emacs 29.0.91 (Org mode 9.6.6)