构建 Go 项目最佳实践

对于项目结构和相关技术栈选择,请查看 项目结构构建 Go Web 服务技术栈

本文内容与前两篇不冲突,侧重于构建略微大一些的项目,如何更好的组织代码使得项目更易维护,以避免 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) 可以依赖外部的类型定义,但不要依赖外部的函数。

First created: 2020-05-13 17:17:51
Last updated: 2020-05-13 Wed 17:43
Power by Emacs 26.3 (Org mode 9.3.6)