先让 go build 和 go test 稳定运行,再重构目录;修复 go.mod、规范 import 路径;cmd/ 只留初始化逻辑,业务下沉至 internal/ 业务域包;用 replace 模拟模块边界,渐进拆分;统一管理工具依赖,优先保障测试通过。

直接重构旧 Go 项目,不是先改目录,而是先让 go build 和 go test 能稳定跑通——否则所有结构调整都是空中楼阁。
确认模块化基础:检查并修复 go.mod 状态
很多老项目还在 GOPATH 模式下裸跑,或 go.mod 存在但被忽略。这会导致 go list 解析失败、gopls 报错、CI 中依赖不一致。
- 运行
go mod edit -fmt格式化现有go.mod,再执行go mod tidy清理未使用依赖和补全缺失项 - 若无
go.mod,在项目根目录执行go mod init example.com/legacy(域名可占位,不需真实注册) - 检查
import路径是否全部匹配文件系统路径:比如import "myproject/internal/user"必须对应./internal/user/目录,不能是./src/internal/user - 禁止在项目里手动创建
src/或test/顶层目录——Go 工具链不认这种结构,会把包路径解析成example.com/legacy/src/internal/user
收敛入口与剥离业务逻辑:重写 cmd/ 目录
老项目常见问题是 main.go 塞满路由注册、DB 初始化、中间件组装、甚至业务 handler。这导致无法复用逻辑、测试困难、新增命令行工具(如 cmd/migrate)时复制粘贴出错。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
cmd/api/main.go只保留三件事:解析 flag(如-port)、加载internal/config、调用internal/app.NewServer().Run() - 把所有 HTTP 路由、gRPC 注册、健康检查端点下沉到
internal/app或更下层的internal/user/http等限界上下文内 - 若原有
main.go有大量初始化逻辑,先抽成internal/bootstrap包,按依赖顺序导出函数(如bootstrap.InitDB()、bootstrap.InitHTTP()),再由internal/app组装 - 确保
cmd/下每个子目录(api、worker、migrate)都只含一个main.go,且不互相 import
按业务域重组 internal/:停用 controllers/ services/ repositories/
看到顶层出现 internal/controllers/ 这类目录,说明职责已严重发散。改用户密码要翻三个目录、改订单状态得同步五处命名,这就是重构的起点。
- 删除
internal/handler、internal/service、internal/repository这类技术分层目录,新建internal/user、internal/order等业务包 - 每个业务包内自行组织:
domain/(模型、领域事件、核心接口)、application/(用例函数,如CreateUser)、infrastructure/(数据库实现、HTTP handler、第三方适配) - 跨业务调用必须通过 domain event 或 async pub/sub,禁止
import "xxx/internal/order/application"—— 若真需要,先抽象出user.OrderService接口,由 order 包实现并注入 - 公共能力(日志、错误码、HTTP 工具)统一收进
internal/pkg/,且不暴露具体实现;外部模块只能import "example.com/legacy/internal/pkg/logger",不能import "example.com/legacy/internal/pkg/logger/zap"
渐进拆分而非一步到位:用 replace 模拟模块边界
别一上来就切 repo、建新仓库、搞语义化版本。90% 的老项目重构卡死在“拆一半不敢动、合回来又白干”。
- 在根
go.mod中添加:replace example.com/legacy/user => ./internal/user,然后其他包就能import "example.com/legacy/user",享受独立go test和go vet - 验证通过后,在
internal/user/go.mod里声明伪模块:module example.com/legacy/user,这样 IDE 和gopls就能正确识别该包为独立单元 - 只有当
user包需独立部署、或由另一团队维护时,才迁出为真实 Git 仓库,并打v1.0.0tag;在此之前,所有变更仍走主干 PR 流程 - 保持
tools.go在根目录,统一管理golangci-lint、swag等开发依赖,避免go install全局污染
重构中最容易被跳过的其实是测试覆盖——没有 go test ./... 能稳定通过的包,任何目录调整都在制造新的技术债。先补关键路径的集成测试,再动结构,比追求“完美目录”实际得多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










