重构基石: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函数应该很「薄」。它只做三件事:

  1. 解析配置
  2. 初始化依赖
  3. 启动应用

业务逻辑?别放这里。我曾经见过有人把整个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」三层。结果每层之间互相调用,改一个接口要改三个文件。后来我改成按业务模块组织,清爽多了。

总结

目录结构这事儿,说白了就是「约定大于配置」。没有绝对的对错,但有一套公认的「好习惯」。

我个人建议:

  1. cmd要薄:只做启动,不做业务
  2. internal要严:私有代码锁起来
  3. pkg要精:只放真正通用的
  4. 按业务分:别按技术分层

嗯,今天就聊到这儿。下一章咱们聊聊「依赖注入」,这可是重构的利器。