本文介绍 Go 项目如何合理组织多个用户界面(如命令行工具、Web 服务)与共享核心逻辑,推荐采用 cmd/ 分层结构,兼顾可维护性、可构建性和 Go 生态惯例。
本文介绍 go 项目如何合理组织多个用户界面(如命令行工具、web 服务)与共享核心逻辑,推荐采用 `cmd/` 分层结构,兼顾可维护性、可构建性 和 go 生态惯例。
在 Go 工程实践中,随着工具功能演进——例如一个密码生成器从纯 CLI 扩展为同时支持终端交互与 Web 界面——项目结构的设计直接影响长期可维护性、依赖隔离性与构建灵活性。Go 官方并无强制规范,但社区已形成高度共识的惯用模式:将可执行入口(entry points)统一收口至 cmd/ 目录下,每个子目录对应一个独立可执行程序;所有业务逻辑与领域能力则下沉为可复用的内部包(如 pkg/ 或直接命名的模块包)。
✅ 推荐结构:清晰分层,职责分明
以密码生成工具为例,推荐如下目录布局:
github.com/yourname/passwordgen/
├── go.mod
├── generation/ # 核心逻辑包:纯函数式 API,无 I/O 依赖
│ ├── generator.go
│ └── options.go
├── cmd/
│ ├── passgen/ # CLI 入口:go build → 生成二进制 `passgen`
│ │ └── main.go
│ └── passgen-web/ # Web 入口:go build → 生成二进制 `passgen-web`
│ └── main.go
└── internal/ # (可选)仅本项目内使用的私有工具或抽象(如日志封装、配置解析)
└── ...generation/ 是真正的“库”:导出 Generate(), ValidateOptions() 等函数,不依赖 fmt, os, net/http 等顶层 I/O 包,确保高可测试性与复用性。
-
cmd/passgen/main.go 示例:
package main import ( "fmt" "log" "os" "github.com/yourname/passwordgen/generation" ) func main() { if len(os.Args) < 2 { log.Fatal("usage: passgen <length>") } n, err := strconv.Atoi(os.Args[1]) if err != nil { log.Fatal(err) } pwd, err := generation.Generate(generation.Options{Length: n}) if err != nil { log.Fatal(err) } fmt.Println(pwd) } -
cmd/passgen-web/main.go 示例(精简版):
package main import ( "fmt" "log" "net/http" "github.com/yourname/passwordgen/generation" ) func handler(w http.ResponseWriter, r *http.Request) { pwd, _ := generation.Generate(generation.Options{Length: 16}) fmt.Fprint(w, pwd) } func main() { http.HandleFunc("/", handler) log.Println("Web server listening on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) }
⚠️ 关键注意事项
- cmd/ 下每个子目录必须是独立的 package main:不能共用一个 main 包,否则 go build 无法区分目标二进制。Go 会自动将 cmd/passgen/ 编译为 passgen 可执行文件(文件名即二进制名)。
- 根目录不放 main.go:避免混淆“库”与“程序”。go get github.com/yourname/passwordgen 默认不会安装任何二进制,符合 Go 模块语义;用户需显式构建 cmd/ 下的子目录。
- 避免 pkg/ 过度抽象:除非存在跨多个项目的通用能力,否则优先使用语义化包名(如 generation, crypto, ui),比泛泛的 pkg/ 更易理解。
- Web 与 CLI 共享逻辑,而非共享 handler 或 flag 解析:UI 层(CLI/Web)只负责输入接收、输出渲染和错误呈现;所有业务规则、算法、验证均在 generation/ 中实现。
✅ 总结:遵循 Go 的“可构建性优先”哲学
这种结构不是教条,而是对 Go 设计哲学的自然延伸:
? 单一职责 —— cmd/ 只负责启动,generation/ 只负责计算;
? 显式依赖 —— 每个 main.go 明确 import 所需包,无隐式耦合;
? 零配置构建 —— go build ./cmd/passgen 和 go build ./cmd/passgen-web 各自产出独立二进制,无需额外脚本或 Makefile;
? 天然支持 go install —— 用户可通过 go install github.com/yourname/passwordgen/cmd/passgen@latest 直接安装 CLI 工具。
当你的工具未来还需增加 TUI(如基于 tcell)、gRPC 接口或 CLI 插件时,只需在 cmd/ 下新增子目录即可——结构稳定、扩展平滑、符合 Go 开发者直觉。


















