Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

1. main 放哪里?

除了library库以外,main.go放在项目根目录下; 既有库又有二进制,可以把main放进cmd目录,非必须;

建议:主程序(CLI / server)放在仓库根目录的 main(或直接 main.go)是最简单也最常用的。

优势之一是安装/运行最短路径。

便于 go install:

最短最干净的安装方式(root 有 main)

go install github.com/eddycjy/project@latest

示例(根目录有 main):

/project
  go.mod
  main.go      // 程序入口
  lib/         // 如果你还有对外库
  internal/    // 可选
  README.md

或者把可复用代码放到一个清晰命名的包,而不是把 main 挪得过远。

2. internal/ 是特性不是仪式

internal/ 的机制是 Go 工具链强制的:internal 下的代码只能被父目录(和子级树)里的包导入。

好处是可以阻止外部依赖,但不是每个项目都需要它。

这时候就涉及到什么时候用?

当你真的有很多对外不暴露但跨包复用的代码,并且项目会被大量第三方使用时才考虑 internal/。

对绝大多数小中型项目来说,不用 internal/ 更简单、更灵活。

示例(使用场景):

/project
internal/
    secrets/   // 只有 project 内部可以 import
pkg/         // 对外可用库(慎用 pkg_/)
main.go

3. 别盲目使用 pkg/

pkg/ 是历史遗留的惯例,在现代 Go 里没必要把所有对外包都塞进 pkg/。

包名与路径应以可读性与语义为核心。把包放到顶层(/auth、/db、/storage)通常更直观,且导入路径更短。

示例对比:

不推荐(多一层 pkg):

import "github.com/you/project/pkg/storage"
更推荐(语义清晰):

import "github.com/you/project/storage"

4. 不要乱建 util、common 等

“工具类” 包看起来方便,但会变成随手塞东西的垃圾仓库。把函数/类型放到语义化更强的包里,或放在最常用的使用位置邻近代码,而不是一个笼统的 util。

反面示例:

// util/strings.go
package util
func Reverse(s string) string { ... }
更好写法(语义化):

// text/reverse.go
package text
func Reverse(s string) string { ... }

// 或者直接放在使用它的包里,例如 handler/text_helpers.go

5. 包不要太多(也别千行一包)

Go 可以在一个包里有多个文件,这一点要善用。每新增一个包,就可能增加依赖、回环风险和迁移成本。相反,也不要把完全不相关的代码塞成一个冗长的包——保持“以用途/语义分包”。

经验规则:

如果一组代码有同一语义与同一生命周期,放到同一个包。 每个包最好能在 200–1000 行范围内(这不是硬性规则,只是可读性提醒)。 切包优先按“用途”而不是“文件大小”。

6. 文件别太细碎

许多人喜欢把每个小函数放不同文件,结果翻代码像翻书页。合理把相关函数聚合到同一文件,便于阅读。

避免把每个 tiny helper 分成独立文件。

7. 语义化命名优先于目录深度

库名、包名与目录名应体现用途。例如 applog 比 util/log 更有意义。

这样看代码的同学通过 import 一眼能看出大致的用途。

8. 版本管理和 semver 建议

建议尽量使用 0.x 阶段语义化版本(保守上 v0.x),在你要打破 API 时给出明确变更说明,而不是过早把版本固定为 v2/v3,导致用户为小改动分叉仓库。

换句话说:先发布、后演进,记录变更而不是封闭。

推荐的最小仓库模板(实战) 下面给出一个适合多数小中型项目的极简布局,能覆盖 CLI / library 混合场景:

/project
go.mod
main.go             // 如果是二进制,把入口放这里
README.md
config/              // 配置相关包
storage/             // 存储逻辑
api/                 // HTTP handler / grpc / rpc
tools/               // 非构建、非导出的脚本 & 工具(可忽略 go build)
docs/

如果你确实需要多个可发布包,再考虑增加清晰命名的子包,而不是 pkg/ 通用层。

Go 官方建议,要关注细节 Go 官方确实给了一份指南:go.dev/doc/modules/layout[1]。

里面有句话经常被曲解:

Larger packages or commands may benefit from splitting… Initially, put them in internal/.

这里的重点其实是 larger 和 may。

结果很多人一上来就机械套用:不管项目大小,先建个 internal/;

现实是要知道目录不是 “一步到位” 的事,需要阶段性调整和设计。

总结 我们要回归 Go 的哲学:简单优先,先能跑,再优雅。多数团队在项目初期做的过度工程(把 internal/、pkg/、cmd/ 都直接套上)更多是为了 “看起来成熟”,但长期结果往往是维护负担增加。

把注意力放在清晰的包命名、合理的功能边界、良好的 README 上,必要时再重构目录结构和演进会比较好。