cmd/下的main.go不能写业务逻辑,因为它是程序入口粘合点,只应加载配置、初始化依赖、调用app.Run();写SQL、路由注册等会导致测试困难、构建报错、复用受阻,且违背Go标准布局中职责分离原则。

cmd/ 下的 main.go 为什么不能写业务逻辑
因为 main.go 是整个程序的入口粘合点,不是业务容器。它只该做三件事:加载配置、初始化依赖(DB、logger、router)、调用 app.Run() 或 server.ListenAndServe()。一旦在里面写 SQL 查询、注册路由、解析参数,就会导致:
• go test 无法单独验证业务逻辑
• go build ./cmd 可能编译出多个 main 包而报错
• 后续要抽离 CLI 工具或 worker 时,得把逻辑从 main.go 里硬抠出来
实操建议:
• 每个可执行程序单独建子目录,如 cmd/api、cmd/migrate
• cmd/api/main.go 中只保留 10–20 行代码,其余全移入 internal/app 或 internal/api
• 若项目用 Gin,路由注册应放在 internal/handler/router.go,由 main.go 调用
internal/ 不是文件夹,是编译器级访问控制开关
Go 编译器会强制拦截任何跨模块对 internal/ 下包的导入,报错 use of internal package not allowed。这不是约定,是硬规则。很多人把它当普通目录用,结果:
• 把通用工具塞进 internal/utils,后期想复用却导不出
• 在 pkg/ 里 import internal/db,导致别人 go get 你的 pkg/logger 时直接失败
实操建议:
• internal/ 下按领域功能建包,如 internal/user、internal/order,而非按技术层(handler、service)平铺
• 模型结构体(User、Order)定义在对应领域包内,不要单独建 models/
• 数据库连接初始化(gorm.Open、sql.Open)留在 cmd/,internal/repository 只依赖抽象接口(如 type UserRepository interface)
立即学习“go语言免费学习笔记(深入)”;
pkg/ 和 config/ 的边界在哪
混淆这两者,等于把项目专用逻辑当成公共组件发布。典型错误是建一个 pkg/config,里面封装了读 config.yaml + fallback 环境变量 + 校验 DB.URL —— 这类代码根本没法被其他项目安全复用。
实操建议:
• pkg/ 只放有稳定接口、带文档和测试、能独立 go get 的东西,例如 pkg/logger(封装 zerolog)、pkg/httpclient(预设超时和重试)
• configs/ 或 internal/config 存项目专属配置文件(config.yaml)和加载逻辑,用 viper.Unmarshal() 直接绑定到结构体即可
• 如果某个配置字段未来可能被外部服务消费(如 OpenAPI 地址),才考虑提取为 pkg/api,否则别动
测试文件必须和源码同目录同包
Go 工具链扫描 go test 时,只认当前模块下与源文件同目录、同包名、后缀为 _test.go 的文件。建个顶层 test/ 目录,会导致:
• go test ./... 找不到测试
• IDE 无法跳转到对应测试
• 想测未导出函数时,因包名不一致而被迫暴露内部字段
实操建议:
• internal/user/service.go 的测试写成 internal/user/service_test.go,包声明仍是 package user
• 黑盒测试(不依赖内部状态)可用 package user_test,但只能调用导出方法
• 集成测试需要启动真实 DB?用 //go:build e2e 标签隔离,运行时加 -tags e2e
import 吗?如果答案是否定的,就别放进 pkg/;如果它只服务于某个领域动作(比如“下单”),就别拆进 internal/handler 和 internal/service 两个包里硬分层。结构不是画出来的,是随着职责收敛自然长出来的。


















