重构基石:Go项目的标准布局
聊到Go项目重构,我第一个想聊的其实是目录结构。
你可能觉得这有什么好说的?不就是建几个文件夹吗?
嗯,我以前也这么想。直到有一次,我接手一个项目,代码全堆在main.go里,一个文件三千行。那感觉,就像走进一个堆满杂物的房间,连落脚的地方都没有。
所以今天,咱们就聊聊Go项目的标准布局。说白了,就是怎么给你的代码「分房间」。
为什么需要标准布局?
Go官方其实没有强制规定目录结构。但社区经过多年实践,形成了一套公认的「最佳实践」。
我个人习惯用这套布局,原因很简单:
- 新人上手快:一看目录就知道代码在哪
- 构建工具友好:go build、go test不用额外配置
- 重构成本低:模块之间边界清晰
核心原则:目录结构应该反映项目的逻辑架构,而不是技术分层。
cmd目录:应用的入口
cmd目录放的是可执行文件的main函数。每个子目录对应一个二进制文件。
myproject/
├── cmd/
│ ├── server/ # 服务端入口
│ │ └── main.go
│ ├── worker/ # 后台任务入口
│ │ └── main.go
│ └── cli/ # 命令行工具入口
│ └── main.go
cmd里的main函数应该很「薄」。它只做三件事:
- 解析配置
- 初始化依赖
- 启动应用
业务逻辑?别放这里。我曾经见过有人把整个HTTP路由定义写在main.go里,那叫一个酸爽。
我的习惯:cmd/main.go一般不超过50行。如果超过,说明你该抽代码了。
internal目录:私有的核心
internal是Go语言级别的访问控制。internal包只能被它的父级目录引用。
myproject/
├── internal/
│ ├── handler/ # HTTP处理器
│ ├── service/ # 业务逻辑
│ ├── repository/ # 数据访问
│ └── model/ # 领域模型
为什么要用internal?
- 防止外部滥用:别人引用你的包时,internal里的东西不可见
- 强制分层:内部模块之间依赖清晰
- 重构安全:改internal代码,不会影响外部用户
注意:internal不是万能药。它只限制目录层级,不限制代码质量。你仍然可能写出「面条代码」。
pkg目录:可复用的公共库
pkg放的是可以被外部项目引用的代码。说白了,就是你的「工具箱」。
myproject/
├── pkg/
│ ├── config/ # 配置解析
│ ├── logger/ # 日志封装
│ ├── middleware/ # HTTP中间件
│ └── utils/ # 工具函数
这里有个坑:别把什么都往pkg里塞。
我见过一个项目,pkg下有几十个包,什么「字符串工具」、「时间工具」、「加密工具」...最后这些工具包之间互相依赖,乱成一锅粥。
判断标准:如果这个包只被你的项目内部使用,放internal。如果它确实有通用价值,再考虑放pkg。
其他常见目录
| 目录 | 用途 | 说明 |
|---|---|---|
| api/ | API定义 | protobuf、OpenAPI等接口描述文件 |
| configs/ | 配置文件 | 默认配置、环境配置模板 |
| scripts/ | 构建脚本 | CI/CD、数据库迁移等 |
| test/ | 外部测试 | 集成测试、端到端测试 |
| docs/ | 文档 | 设计文档、API文档 |
实战:一个完整的目录结构
说了这么多,咱们看个完整的例子。这是我最近重构的一个项目:
myproject/
├── cmd/
│ ├── server/
│ │ └── main.go
│ └── worker/
│ └── main.go
├── internal/
│ ├── handler/
│ │ ├── user.go
│ │ └── order.go
│ ├── service/
│ │ ├── user.go
│ │ └── order.go
│ ├── repository/
│ │ ├── user.go
│ │ └── order.go
│ └── model/
│ ├── user.go
│ └── order.go
├── pkg/
│ ├── config/
│ │ └── config.go
│ └── logger/
│ └── logger.go
├── api/
│ └── proto/
│ └── user.proto
├── configs/
│ ├── config.yaml
│ └── config.dev.yaml
├── scripts/
│ └── migrate.sh
├── test/
│ └── integration/
│ └── user_test.go
├── go.mod
└── go.sum
你想想看,这个结构有什么好处?
- 新人来了,直接看internal/handler就知道接口在哪
- 改数据库逻辑,去internal/repository找
- 加个新功能,按目录结构往里填代码就行
避坑指南:我曾经把一个项目的internal目录拆成「dao」、「service」、「controller」三层。结果每层之间互相调用,改一个接口要改三个文件。后来我改成按业务模块组织,清爽多了。
总结
目录结构这事儿,说白了就是「约定大于配置」。没有绝对的对错,但有一套公认的「好习惯」。
我个人建议:
- cmd要薄:只做启动,不做业务
- internal要严:私有代码锁起来
- pkg要精:只放真正通用的
- 按业务分:别按技术分层
嗯,今天就聊到这儿。下一章咱们聊聊「依赖注入」,这可是重构的利器。