创建一个好的Go项目结构并不是一件容易的事情,由于Go语言在设计包和模块方面提供了很大的自由度,因此在这方面没有通用的最佳实践。本文将首先讨论创建项目的常用组织结构,然后讨论一些最佳实践,给出改进项目组织方式的方法。
项目结构模板
Go语言维护者对构建Go项目结构没有严格的约定,在github上有一个称为标准Go项目结构的模板(github.com/golang-stan...%25EF%25BC%2589%25EF%25BC%258C%25E6%25B3%25A8%25E6%2584%258F%25E8%25AF%25A5%25E6%25A8%25A1%25E6%259D%25BF%25E4%25B8%258D%25E6%2598%25AFGo%25E5%25AE%2598%25E6%2596%25B9%25E6%258F%2590%25E4%25BE%259B%25E7%259A%2584%25E3%2580%2582%25E5%25A6%2582%25E6%259E%259C%25E6%2588%2591%25E4%25BB%25AC%25E7%259A%2584%25E9%25A1%25B9%25E7%259B%25AE%25E5%25BE%2588%25E5%25B0%258F%25EF%25BC%2588%25E5%258F%25AA%25E6%259C%2589%25E5%2587%25A0%25E4%25B8%25AA%25E6%2596%2587%25E4%25BB%25B6%25EF%25BC%2589%25EF%25BC%258C%25E6%2588%2596%25E8%2580%2585%25E5%2585%25AC%25E5%258F%25B8%25E5%2592%258C%25E9%25A1%25B9%25E7%259B%25AE%25E7%25BB%2584%25E5%25B7%25B2%25E7%25BB%258F%25E6%258C%2587%25E5%25AE%259A%25E4%25BA%2586%25E9%25A1%25B9%25E7%259B%25AE%25E7%25BB%2593%25E6%259E%2584%25E8%25A7%2584%25E8%258C%2583%25EF%25BC%258C%25E9%2587%258D%25E6%2596%25B0%25E8%25B0%2583%25E6%2595%25B4%25E6%2588%2596%25E8%25BF%2581%25E7%25A7%25BB%25E5%2588%25B0%25E4%25B8%258A%25E8%25BF%25B0%25E6%25A8%25A1%25E6%259D%25BF%25E6%25A0%25BC%25E5%25BC%258F%25E5%258F%25AF%25E8%2583%25BD%25E4%25B8%258D%25E5%2580%25BC%25E5%25BE%2597%25E3%2580%2582%25E5%25A6%2582%25E6%259E%259C%25E9%25A1%25B9%25E7%259B%25AE%25E8%25BF%2598%25E6%25B2%25A1%25E6%259C%2589%25E7%25BB%2593%25E6%259E%2584%25E8%25A7%2584%25E8%258C%2583%25EF%25BC%258C%25E9%2582%25A3%25E5%2589%258D%25E9%259D%25A2%25E8%25BF%2599%25E4%25B8%25AA%25E7%25BB%2593%25E6%259E%2584%25E5%2580%25BC%25E5%25BE%2597%25E5%258F%2582%25E8%2580%2583%25E5%2580%259F%25E9%2589%25B4%25E3%2580%2582%25E7%258E%25B0%25E5%259C%25A8%25E6%2588%2591%25E4%25BB%25AC%25E6%259D%25A5%25E7%259C%258B%25E7%259C%258B%25E8%25BF%2599%25E4%25B8%25AA%25E7%25BB%2593%25E6%259E%2584%25E6%25A8%25A1%25E6%259D%25BF%25E7%259A%2584%25E5%25B8%2583%25E5%25B1%2580%25EF%25BC%258C%25E9%2583%25BD%25E6%259C%2589%25E4%25BA%259B%25E4%25BB%2580%25E4%25B9%2588%25E5%2586%2585%25E5%25AE%25B9%25EF%25BC%259A "https://github.com/golang-standards/project-layout)%EF%BC%89%EF%BC%8C%E6%B3%A8%E6%84%8F%E8%AF%A5%E6%A8%A1%E6%9D%BF%E4%B8%8D%E6%98%AFGo%E5%AE%98%E6%96%B9%E6%8F%90%E4%BE%9B%E7%9A%84%E3%80%82%E5%A6%82%E6%9E%9C%E6%88%91%E4%BB%AC%E7%9A%84%E9%A1%B9%E7%9B%AE%E5%BE%88%E5%B0%8F%EF%BC%88%E5%8F%AA%E6%9C%89%E5%87%A0%E4%B8%AA%E6%96%87%E4%BB%B6%EF%BC%89%EF%BC%8C%E6%88%96%E8%80%85%E5%85%AC%E5%8F%B8%E5%92%8C%E9%A1%B9%E7%9B%AE%E7%BB%84%E5%B7%B2%E7%BB%8F%E6%8C%87%E5%AE%9A%E4%BA%86%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84%E8%A7%84%E8%8C%83%EF%BC%8C%E9%87%8D%E6%96%B0%E8%B0%83%E6%95%B4%E6%88%96%E8%BF%81%E7%A7%BB%E5%88%B0%E4%B8%8A%E8%BF%B0%E6%A8%A1%E6%9D%BF%E6%A0%BC%E5%BC%8F%E5%8F%AF%E8%83%BD%E4%B8%8D%E5%80%BC%E5%BE%97%E3%80%82%E5%A6%82%E6%9E%9C%E9%A1%B9%E7%9B%AE%E8%BF%98%E6%B2%A1%E6%9C%89%E7%BB%93%E6%9E%84%E8%A7%84%E8%8C%83%EF%BC%8C%E9%82%A3%E5%89%8D%E9%9D%A2%E8%BF%99%E4%B8%AA%E7%BB%93%E6%9E%84%E5%80%BC%E5%BE%97%E5%8F%82%E8%80%83%E5%80%9F%E9%89%B4%E3%80%82%E7%8E%B0%E5%9C%A8%E6%88%91%E4%BB%AC%E6%9D%A5%E7%9C%8B%E7%9C%8B%E8%BF%99%E4%B8%AA%E7%BB%93%E6%9E%84%E6%A8%A1%E6%9D%BF%E7%9A%84%E5%B8%83%E5%B1%80%EF%BC%8C%E9%83%BD%E6%9C%89%E4%BA%9B%E4%BB%80%E4%B9%88%E5%86%85%E5%AE%B9%EF%BC%9A")
-
/cmd 项目主要的应用程序. foo应用程序的main.go应该位于/cmd/foo/main.go中。
-
/internal 私有的应用程序代码库,这里面的代码是不希望被其它人导入的。
-
/pkg 外面的应用程序可以使用的代码库,是向其它人公开的公共代码。
-
/test 存储测试数据和代码。Go语言中的单元测试文件与源文件通常都在一个包中。但像公共API测试或集成测试代码应该存放在/test中。
-
/configs 存放配置文件
-
/docs 存放设计和用户文档
-
/examples 应用程序或公共库函数的实例程序
-
/api api接口定义文件(Swagger, Protocol Buffers等)
-
/web web应用程序的资源文件(静态图片等)
-
/build 打包和持续集成(CI)文件
-
/scripts 用于分析、安装等脚本文件
-
/vendor 应用程序的依赖文件(例如Go模块的依赖库)
可以看到上面的标准结构中没有/src目录,这是因为/src目录太泛了,因此采用了/cmd、/internal和/pkg这种目录。
NOTE:在2021年,Go语言的核心维护者 Russ Cox对上面的项目结构表达反对意见。尽管它号称是Go项目标准结构,但不是官方的标准,有误导人嫌疑。对于项目结构,没有强制性约定必须采用上述模板。我们必须意识到这一个点,唯一注意的是项目中的各个模块结构要保持一致,达成统一。避免在不同的结构之间发生迁移,这会浪费时间。
包组织结构
在Go语言中,没有子包的概念。但是,我们可以在子目录中创建包。下面是标准库net中的目录结构。net既充当包,又充当包含其他包的目录。但是net/http包不继承net或对net包具有特定的访问权限。外界能看到net/http中可导出的元素。子目录的主要好处是将包中代码保存在具有高内聚性的地方。
console
/net
/http
client.go
...
/smtp
auth.go
...
addrselect.go
...
对于Go包的组织形式,有不同的观点。例如,我们应该按业务类型还是按层来组织应用程序,这取决于自己的喜好。我们可能倾向于按业务类型(例如客户业务,合同业务等)对代码进行分组,或者我们倾向于遵循六边形原则对其进行分组。只要选择出了适合我们的方法,保持统一即可。
对于软件包,我们应该遵循一些最佳实践。首先,应该避免过渡设计,因为这可能会使得项目过于复杂。当我们搞清楚了项目包含的内容后,最好使用一个简单的形式组织并让项目不断的发展,而不是强迫自己预先制定完美的结构。
包的粒度是另一个需要考虑的重要因素,我们应该避免有几十个包含一两个文件的小包。如果这样设计,可能错过了这些包之间的一些逻辑联系,使得项目更难让人理解。此外,我们也应该避免使用包含很多文件的大包。总之,对于包的粒度,我们不应该走极端,导致包极小或极大。
包的命名也应该谨慎考虑。众所周知,命名是程序开发中一件困难的事情。为了帮助用户理解Go项目,我们应该根据它提供的内容命名包,而不是它包含的内容。此外,包名要有意义。因此,包的名称应该简短、简洁和富有表现力,按照惯例,应该是一个小写单词。
对于包导出什么,规则非常简单。我们应该尽可能减少应该导出的内容,以减少包之间的耦合并隐藏不必要导出的元素。如果不确定是否要导出一个元素,应该默认它不导出,在后面发现需要导出时,再调整代码支持将其导出。我们还要注意一些特殊情况,例如,当我们对一个结构体对象调用 encoding/json 标准库对其进行序列化或反序列化时,该结构体对象的字段需要是可导出的(即首字母要大写),否则会忽略该字段。
组织好一个项目结构并不是一件简单的事情,遵循上述这些规则有助于我们更容易维护。记住一点,保持结构一致对于简化可维护非常有帮助。因此,应确保代码库中的代码尽可能保持一致。