用途
这是一份关于如何用 Go 开发应用与模块/库的指南。它建议在任务以任何方式涉及 Go 时(例如创建或编辑任意 .go 文件)始终将该技能作为第一动作调用,先于任何 Read、Write、Edit 或 Bash 操作。指南中部分内容仅适用于应用,不适用于作为库供其他项目使用的模块,例如数据库访问和运行服务器。
应用结构
作者通常构建 Web 应用和库/模块。应用里常见的包包括(部分可能缺失,通常意味着项目不需要):
- main:应用主入口(位于目录 cmd/app)
- model:贯穿其他包的领域模型
- sql/sqlite/postgres:SQL 数据库相关逻辑、数据库迁移(migrations/ 子目录)与测试夹具(testdata/fixtures/ 子目录);数据库为 SQLite 或 PostgreSQL
- sqltest/sqlitetest/postgrestest:测试用包,用于建立和拆除测试数据库
- s3:与 Amazon S3 或兼容对象存储交互的逻辑
- s3test:测试用包,用于建立和拆除测试 S3 桶
- llm:与大语言模型(LLM)和基础模型交互的客户端
- llmtest:测试用包,用于为测试建立 LLM 客户端
- http:应用的 HTTP 处理器
- html:应用的 HTML 模板,使用 gomponents 库编写(需要时参见 https://www.gomponents.com/llms.txt)
代码风格与依赖注入
作者在组件之间大量使用依赖注入,通常在接收方使用私有接口。指南给出了 http 包中使用 userGetter 接口的示例代码,展示了如何定义私有接口、注入依赖并编写处理器。
测试
作者为大多数函数和方法编写测试,几乎总是使用子测试,并带有对正在发生什么、期望结果是什么的良好描述。
测试文件命名规则:对于 example.go,测试文件始终是 example_test.go,从不使用 example_this_feature_test.go。唯一例外是需要变更测试包(内部测试)时,文件为 example_internal_test.go。
指南给出了基本测试示例和表驱动测试示例,使用 maragudk/example 与 maragu.dev/is 断言库。
作者更偏好使用真实依赖的集成测试而非 mock,因为真实的依赖无可替代。依赖通常在 Docker 容器中运行。可以假定运行测试时依赖已在运行。当测试的重要部分不是依赖、依赖只起较小作用时,使用 mock 才合理;但例如测试数据库方法时,应使用真实的底层数据库。
测试断言使用 maragu.dev/is 模块,可用函数包括:is.True、is.Equal、is.Nil、is.NotNil、is.EqualSlice、is.NotError、is.Error。这些都接受一个可选消息作为最后一个参数。
由于测试会被打乱,不要依赖测试顺序,即使是对子测试也一样。
每次调用 postgrestest.NewDatabase(t)/sqlitetest.NewDatabase(t) 测试助手时,数据库都处于干净状态(没有其他测试的残留等)。
可使用数据库夹具(fixtures)进行测试。当多个测试依赖相同或非常相似的数据时,优先使用它们来准备测试数据,以免每个测试都要设置同样的数据。夹具位于 sqlite/testdata/fixtures 与 postgres/testdata/fixtures。通过 sqlitetest.NewDatabase(t, sqlitetest.WithFixtures("fixture one", "fixture two")) 使用,按给定顺序应用。
测试辅助函数应调用 testing.T.Helper()。
测试中使用 t.Context() 而非 context.Background(),并且始终内联使用,不要提取为 ctx 变量。
涉及计时的测试——超时、重试、ticker、任何休眠或等待时钟的逻辑——应使用 testing/synctest 包(自 Go 1.25 起稳定)。它在假时钟上运行测试,使计时测试快速且确定,而不是缓慢且易失败。
杂项
变量命名:请求用 req,响应用 res。
可用的 SQL 辅助方法:Database.H.Select、Database.H.Exec、Database.H.Get、Database.H.InTx。
使用 Go 内置的 any 而非 interface{}。
标准库 sql.ErrNoRows 有别名:maragudk/glue/sql.ErrNoRows,因此不必同时导入两者。
所有 HTML 按钮都需要 cursor-pointer CSS 类。
SQLite 时间格式始终是 strftime('%Y-%m-%dT%H:%M:%fZ') 返回的字符串。与数据库协作时使用 maragudk/glue/model.Time(通常在项目中别名为 model.Time)而非标准库 time.Time。
记住 Go 中私有函数是包级作用域,因此可以在同一包的不同文件间使用它们。
文档必须遵循 Go 风格:标识符名称作为句子第一个词,然后完成句子而不重复自身。示例:“// SearchProducts using the given search query and result limit.” 而不是:“// SearchProducts searches products using the given search query and result limit.”
在文档注释中用 [identifier] doclink 语法链接你引用的标识符,例如 [http.HandlerFunc]、[context.Canceled],包括同一包中未导出的标识符。永远不要链接开头的标识符(被文档化的符号)——那个是纯文本,因为你不应把它链接到自身。
包中的注释从不向外看:包不知道谁导入它,因此不要提及调用方或其包(例如“used by the http handlers”)。引用直接依赖是可以的——那是它自身故事的一部分——但只引用它们的公共 API:当一个包依赖某个接口时,注释描述该契约,而绝不描述其背后的具体实现。
注释描述代码现在的样子,而非过去的样子。永远不要留下历史笔记(“previously this used X”、“replaced the old Y”)——旧代码一旦消失,这类注释只会让人困惑。该历史属于 diary(参见 [[diary]] 技能)和 git。
包级标识符默认必须以小写开头,即具有包级可见性,以缩小对其他包的 API 表面积。
不要把只在一处使用的值提取为常量——超时、请求头的字符串字面量、查询键等最好在使用处内联阅读。即使在一个函数内使用几次也可以保持内联。当值跨函数复用时,或名称能增加真正含义时,才提取常量,不要提前提取。
将任意值转换为字符串时使用 fmt.Sprint,而非 strconv 中的函数。
使用 new()(自 Go 1.26 起可用)而非任何指针函数来创建字面量的指针。
测试、lint、eval
运行 make test 或 go test -shuffle on ./... 来运行所有测试。只运行某个包中的测试使用 go test -shuffle on ./path/to/package。运行特定测试使用 go test ./path/to/package -run TestName。
运行 make lint 或 golangci-lint run 来运行 linter。它们应始终在包/目录级别运行,对单个文件不生效。
运行 make eval 或 go test -shuffle on -run TestEval ./... 来运行 LLM eval。
运行 make fmt 来格式化项目中的所有代码,作为最后的收尾很有用。
可以在 shell 中使用 psql 或 sqlite3 访问数据库。
文档查阅
一般可以用 go doc 加模块名来查阅 Go 模块文档。例如 go doc net/http 查阅标准库内容,或 go doc maragu.dev/gai 查阅第三方模块。也可用 go doc maragu.dev/gai.ChatCompleter 查阅某个标识符(如 ChatCompleter 接口)的更具体文档。
在浏览器中检查应用
可以假定应用正在运行,并通过 Chrome Dev Tools MCP 工具在浏览器中访问。它会在代码变更时自动重载,无需手动操作。运行中应用的日志输出在项目根目录的 app.log 中。
限制
以上内容为指南所提供的事实,未提供安装命令、收费或价格信息,也未提供平台与效果说明。