
柒啊
2023/03/30阅读:43主题:前端之巅同款
[3分钟]GO:项目结构及目录
四、项目结构
我们来谈谈如何将包组合到项目中。 通常一个项目是一个 git
仓库,但在未来 Go
语言开发人员会交替地使用 module
和 project
。
就像包一样,每个项目都应该有一个明确的目的。 如果你的项目是一个库,它应该只提供一件事,比如 XML 解析或log记录。 您应该避免将多种目的或功能放到一个项目中, 这会避免写出一个糟糕的 common
库。
Tips:根据我的经验,
common
库最终会与其最大的调用者紧密耦合,这使得在不同步升级common
库和调用者的情况下很难向后移植修复程序,从而带来许多不相关的更改和 API 损坏。
如果你的项目是一个应用程序,比如你的Web应用程序、Kubernetes控制器等等,那么你的项目里面可能有一个或多个main
包。例如,我工作的Kubernetes控制器有一个cmd/contour包,它既是部署在Kubernetes集群上的服务器,又是用于调试的客户端。
4.1 考虑更少、更大的包
对于从其他语言过渡到 Go 语言的程序员来说,我常常在代码审查中提到的一件事是他们会过度使用包。
Go
语言没有提供有关可见性的详细方法; Java
有 public
、protected
、private
以及隐式 default
的访问修饰符。 没有 C++
的 friend
类概念。
在 Go
语言中,我们只有两个访问修饰符,public
和 private
,由标识符的第一个字母的大小写表示。 如果标识符是公共的,则其名称以大写字母开头,该标识符可用于任何其他 Go
语言包的引用。
Note: 你可能会听到人们说 exported 与 not exported, 这跟 public 和 private 是一样的。
鉴于包的符号的访问有限控件,Go
程序员应遵循哪些实践来避免创建过于复杂的包层次结构?
Tips: 除 cmd/ 和 internal/ 之外的每个包都应包含一些源代码。
我总是建议宁愿选择少一点,大一点的包。 你的默认立场应该是不创建新的包。 这将导致太多类型被公开,这会导致你的包有一个宽而浅的API。 【这里理解宽而浅的API是有很多API但是却没有深层次的调用,只有一层的API】
以下部分将更为详细地探讨这一建议。
Tips:来自
Java
?如果您来自 `Java` 或 `C#`,请考虑这一经验法则 -- `Java` 包相当于单个 `.go` 源文件。 - `Go` 语言包相当于整个 `Maven` 模块或 `.NET` 程序集。
4.1.1. 通过 import 语句将代码整理到文件中
如果你通过Go
提供给开发者的东西来管理你的包【我觉得这里说的是 import
】,你是不是也应该使用相同的方法来管理包里的文件?你知道什么时候应该将一个.go
文件分割成多个吗?你知道什么时候应该考虑将多个包 合并成一个吗?
这里是我日常使用的指导规则:
-
一个包刚开始只使用一个文件,文件使用与包名相同的文件名。比如: package http
应该放在http
目录下的http.go
文件中。 -
随着包的增长,你可能决定将各种职责拆分到不同的文件中。 比如, messages.go
包含Request
和Response
类型,client.go
包含Client
类型,server.go
包含Server
类型. -
如果你的文件中 import
的声明类似,请考虑将它们组合起来。或者确定 import 集之间的差异并移动它们。 -
不同的文件应该负责包的不同区域。 messages.go
可能负责网络的HTTP
请求和响应,http.go
可能包含底层网络处理逻辑,client.go
和server.go
实现HTTP
业务逻辑请求的实现或路由等等。
Tips:源文件名应该以名词作为首选。
Note:Go编译器并行编译每个包。 在一个包中,编译器并行编译每个函数(方法只是 Go 语言中函数的另一种写法)。 更改包中代码的布局不会影响编译时间。
4.1.2. 优先内部测试再到外部测试
go tool
支持在两个地方编写 testing
包测试。假设你的包名为 http2
,您可以编写 http2_test.go
文件并使用包 http2
声明。 这样做会编译 http2_test.go
中的代码,就像它是 http2
包的一部分一样。这就是内部测试。
go tool
还支持一个特殊的包声明,以 test
为结尾,即 package http_test
。这允许你的测试文件与代码一起存放在同一个包中, 但是当编译时这些测试不是包的代码的一部分,它们存在于自己的包中。就像调用另一个包的代码一样来编写测试。这被称为外部测试。
我建议在编写单元测试时使用内部测试。这样你就可以直接测试每个函数或方法,避免外部测试干扰。
但是,你应该将 Example
测试函数放在外部测试文件中。这确保了在 godoc
中查看时,示例具有适当的包名前缀并且可以轻松地进行复制粘贴。
Tips:
避免复杂的包层次结构,抵制应用分类法
Go
语言包的层次结构对于go tool
没有任何意义除了下一节要说的。 例如,net/http
包不是一个子包或者net
包的子包。如果在项目中创建了不包含
.go
文件的中间目录,则可能无法遵循此建议
4.1.3 使用 internal
包来减少公共API
如果项目包含多个包,可能有一些公共的函数,这些函数旨在供项目中的其他包使用,但不打算成为项目的公共API的一部分。 如果你发现是这种情况, 那么 go tool
会识别一个特殊的文件夹名称 - 而非包名称 - internal/
可用于放置对项目公开的代码,但对其他项目是私有的。
要创建这样的包,请将其放在名为 internal/
的目录中或放在名为 internal/
的目录的子目录中。当 go
命令看到在其路径中包含 internal
的包的导入时, 它会验证执行导入的包是否在以 internal
目录的父目录为根的树中。
例如,.../a/b/c/internal/d/e/f
的包只能通过以 .../a/b/c/
为根目录的代码被导入。 它无法通过 .../a/b/g
或任何其他仓库中的代码导入。
4.2. 确保 main
包内容尽可能的少
main
函数和 main
包的内容应尽可能少。 这是因为 main.main
充当单例; 程序中只能有一个 main
函数,包括 tests
。
因为 main.main
是一个单例,假设 main
函数中需要执行很多事情,main.main
只会在 main.main
或 main.init
中调用它们并且只调用一次。 这使得为 main.main
编写代码测试变得很困难,因此你应该将所有业务逻辑从 main
函数中移出,最好是从 main
包中移出。
Tips:
main
应该做解析flags
,开启数据库连接、开启日志等,然后将执行交给更高一级的对象。
内容学习于该博客:英文博客
同时借鉴于该翻译:中文翻译
作者介绍
