go项目结构是工程能否运行的基础而非风格选择:cmd/须按可执行名建子目录,internal/为编译器强制私有边界,测试文件必须与源码同目录同包,go mod init需显式指定匹配import路径的模块名。

Go 项目结构不是“怎么好看怎么来”,而是直接影响 go build 能否成功、go test 能否自动发现用例、import 路径是否稳定——这不是风格问题,是工程能否跑起来的基础。
cmd/ 下必须按可执行名建子目录
Go 工具链靠目录名决定最终二进制名:cmd/api/main.go → go build 输出 ./api;若写成 cmd/main.go,输出就是 ./cmd,和预期不符。更关键的是,多个命令(如 CLI + worker)共存时,go build ./cmd 会报 multiple main packages 错误。
-
cmd/每个子目录对应一个独立可执行文件,目录名即二进制名(如cmd/steambot→ 生成steambot) -
main.go只做三件事:加载配置、初始化依赖、调用app.Run()—— 所有业务逻辑必须移出 - 不要在
cmd/下放任何非main包代码;否则go list ./会误判包路径
internal/ 是编译器级私有边界,不是“私有代码垃圾桶”
internal/ 不是约定,是 Go 语言硬性规则:任何位于 github.com/user/project/internal/foo 的包,**仅允许被 github.com/user/project/ 下的包导入**;外部模块引用直接报错 use of internal package not allowed。
- 把真正不该被外部依赖的实现细节放这里,比如
internal/tradebot/(含状态机、私有回调)、internal/dbmigration/ - 避免
internal/util/或internal/common/—— 这类命名等于放弃语义,最终变成函数堆砌场 - 如果某个包未来可能开源复用,就别放
internal/,改放pkg/或直接顶层(如github.com/user/project/storage)
go mod init 必须显式指定与 import 路径一致的模块名
不加参数直接 go mod init 会生成错误的模块名(比如 github.com/username/project),而实际你可能用的是私有 Git 地址或本地路径。后续依赖解析、go get、CI 构建都会出问题,尤其在私有仓库或 monorepo 场景下。
- 正确做法是:在项目根目录执行
go mod init example.com/myapp,模块路径必须与你最终import的路径一致——哪怕只是本地开发,也应模拟真实导入路径(如myapp就不行,example.com/myapp才对) - 模块路径不能以
./或相对路径开头 - 如果项目托管在 Git,建议和远程 URL 的域名/路径保持一致(如
gitlab.internal/foo/bar→ 模块名设为gitlab.internal/foo/bar) -
go.mod生成后立即运行go mod tidy,它会自动补全缺失依赖并清理未使用项,但前提是import语句已写完
测试文件必须与源码同目录同包,且命名严格匹配
Go 不支持集中式 test/ 目录。go test ./ 只扫描当前模块下所有 *_test.go 文件,并要求它们与同目录的非 _test.go 文件属于同一包(package xxx),否则编译失败。
-
internal/tradebot/tradebot.go(package tradebot)的测试必须是internal/tradebot/tradebot_test.go(同样package tradebot) - 若想测私有函数,测试文件必须和源文件同包;若想黑盒测导出接口,可用
package tradebot_test,但此时无法访问未导出字段或方法 - 不要把测试文件放在
test/目录下 ——go test根本不会识别
最容易被忽略的是 internal/ 的语义约束和 cmd/ 的命名刚性:它们不是“推荐”,而是编译器强制执行的边界;一旦越界,错误往往出现在 CI 或他人拉取代码后,而不是你本地写完就立刻暴露。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











