构建和运行用的 Go 版本必须在官方支持窗口内(最新的两个大版本),并及时升级到最新补丁版
go version,与官方发布列表比对;超出支持窗口时失败主题 / 语言
Go 项目的工具链与模块、格式化与 lint、错误处理、context、并发、测试、日志、HTTP 服务超时、数据库访问写法、迁移工具和漏洞扫描怎么做。
go.mod 的 go 行写最低语言版本,构建用的工具链另外固定。go mod tidy 没有差异;发布构建不用 @latest。%w 包装,用 errors.Is / errors.As 判断。context.Background()。-race,停机路径用 goleak 查泄漏。http.Server 自己设各项超时,客户端不用没有超时的 http.DefaultClient,请求体限大小。rows.Err()。迁移默认 goose。LogValuer 脱敏。go version,与官方发布列表比对;超出支持窗口时失败go 行写项目需要的最低语言版本;构建用的工具链用 toolchain 行或构建镜像固定到补丁版,CI 和生产一致toolchain 行或版本文件一致go 行是语言要求,不是构建版本;只写一个大版本号,不同机器会用不同补丁版构建出不同结果。go 版本,不要因为自己升级了就抬高,否则会逼着调用方升级。GOTOOLCHAIN=local,禁止构建时自动下载工具链,所需版本预置在镜像里。-mod=readonly 构建,并检查 go mod tidy 后没有差异GOFLAGS=-mod=readonly,运行 go mod tidy -diff 或 tidy 后 git diff --exit-code go.mod go.sum,再跑 go mod verifygo install …@latest 或 go get …@latest;开发工具固定版本@latest;工具用 go.mod 的 tool 指令或写死版本安装GOPRIVATE(或 GONOSUMDB)限定到具体域名或路径,不在组织层关闭校验和数据库(GOSUMDB=off)GOSUMDB=off 出现时要求说明GOPROXY=off(vendor)或指向内部代理、GOTOOLCHAIN=local,跑构建和测试golangci-lint fmt --diff,有输出即失败;或 gofmt -l . 输出非空即失败golangci-lint config verify)再运行golangci-lint config verify 和 golangci-lint run ./...;v1 配置先用 golangci-lint migrate 转换linters.default 取 standard、all、none、fast 之一,再用 enable / disable 调整。formatters,不能再写进 linters.enable。default: all 再大面积关闭.golangci.yml:检查推荐集合是否启用、关闭项是否写明理由nolint 压下去,真正的问题反而被淹没。推荐起步集合:govet、staticcheck、errcheck、errorlint、ineffassign、unused、bodyclose、noctx、contextcheck、containedctx、rowserrcheck、sqlclosecheck、gosec、revive、nolintlint、misspell。
需要约束导入时加 depguard;gocritic 逐项评估后再开。
//nolint 必须写明具体 linter 和理由;用 nolintlint 强制require-specific: true、require-explanation: truego vet ./... 并且没有报告go vet ./...,或在 golangci-lint 中启用 govet_ = 并注释原因fmt.Errorf("…: %w", err) 包装;判断错误用 errors.Is / errors.As,不用 == 比较包装过的错误,也不匹配错误消息文本%w、比较和类型断言)== 和字符串匹配遇到包装就失效。多个错误同时返回用 errors.Join。上下文写操作和对象,如 fmt.Errorf("load order %s: %w", id, err),不要以 "failed to" 开头堆叠。
%w;不想暴露内部依赖的错误用 %v,并导出哨兵错误或错误类型供判断%w 包装的底层错误是否成了 API 的一部分init 里不做网络、文件等外部 I/Opanic、log.Fatal 和 init 函数程序启动时配置非法可以在 main 里直接退出;库代码一律返回 error。
ctx context.Context 并传给下游,调用带 Context 的 API(如 QueryContext、http.NewRequestWithContext)context.Background() 或 context.TODO() 代替传入的 ctxcontext.Background() 要说明为什么不用上游 ctx请求结束后仍需继续的工作(如审计写入、清理),用 context.WithoutCancel(ctx) 保留值但不继承取消,并另设超时;真正的后台任务交给任务队列(见 18),不要在请求里 go func() 了事。
context.WithCancel / WithTimeout / WithDeadline 返回的 cancel 必须调用,通常紧跟一行 defer cancel()context.WithValue 的调用go 语句的退出条件;在停机和关键组件的测试中用 goleak 检查泄漏errgroup.WithContext 加 SetLimit(或 semaphore)限制并发数,不按输入数量无限启动 goroutinego 语句和 WaitGroup 用法go test -race 跑测试,发现竞态即失败go test -race ./...time.Ticker 承担;需要持久、重试、定时的工作交给任务队列或调度系统go func() 和 Ticker 是否承载了进程重启后不能丢的工作t.Run);复杂结构比较用 go-cmpgithub.com/golang/mockFuzzXxx;定时任务运行 go test -fuzz=… -fuzztime=…go test 会把种子语料当单元测试跑,成本很低。fuzz 发现的失败输入会写进 testdata/fuzz/,要入库作为回归用例。
log.Printf 和第二套日志库log 包的打印函数和未批准的日志库slog.LogValuer(或 String())返回脱敏后的值http.Server 必须显式设置 ReadHeaderTimeout、ReadTimeout、WriteTimeout(流式接口除外)和 IdleTimeout;不用无超时的 http.ListenAndServeClient.Timeout 或请求 ctx 的截止时间),不用 http.Get 和默认 http.DefaultClient 调外部服务;响应体必须关闭http.Get、http.Post、http.DefaultClienthttp.MaxBytesReader 限制大小,不对不可信输入直接 io.ReadAllsignal.NotifyContext 接收信号,http.Server.Shutdown 带截止时间;后台 worker 处理完手头工作再退出os.Exit 会中断进行中的请求和事务;Shutdown 不带截止时间可能永远等下去。fmt.Sprintf 或字符串拼接把值写进 SQL;表名、列名等动态标识符只能从白名单里选BeginTx 开启,紧跟 defer tx.Rollback(…),并检查 Commit 返回的错误;事务里的语句都用 tx 执行db 而不是 tx 执行语句,会落到另一个连接上,不在事务内。rows.Err()git diff --exit-code 必须无差异go generate ./... 或 sqlc generate,再 git diff --exit-code生成文件头部要有 // Code generated … DO NOT EDIT.,linter 会据此跳过。
internal/ 控制可见性go-arch-lint check;golangci-lint 启用 depguard不照搬社区流传的「标准项目布局」,它不是官方规定;按官方的模块布局指南从简单结构开始。
govulncheck ./...,存在被代码实际调用到的已知漏洞时失败;发布前在最终提交上再跑一次只出现在依赖里、代码没调用到的漏洞会单独列出,不阻断,但要排期升级。容器镜像和系统包的扫描见 25。
没有符合条件的规则。
| 类别 | 默认 | 备选(何时用) | 不推荐 |
|---|---|---|---|
| 工具链版本 | go.mod 的 go 与 toolchain 行 + 固定补丁版的构建镜像 | mise 或 asdf(一台机器上要管多种语言的版本) | 开发、CI、生产各用各的版本:结果不可复现,补丁版之间也有行为修复 |
| 格式化 | gofmt + goimports(通过 golangci-lint 的 formatters 执行) · gofmt 随 Go 发行(BSD-3-Clause);gofumpt 为 BSD-3-Clause | gofumpt(团队想要更严格的统一格式,全仓一起切换) | 同一仓库混用不同格式器:来回改格式,产生无意义的差异 |
| lint | golangci-lint v2,配合 GO-009 的推荐集合 · golangci-lint 和 depguard 为 GPL-3.0;作为 CI 里独立运行的工具使用,不链接进产品(口径见 27) | staticcheck 等单独运行(只需要少数检查、不想引入聚合工具) | default: all 再加大量 nolint:误报淹没真问题 |
| 漏洞扫描 | govulncheck · BSD-3-Clause | Trivy、Grype 等通用扫描器(还要扫容器镜像和系统包(见 25)) | — |
| HTTP 路由 | 标准库 net/http 的 ServeMux(支持方法和路径参数) · chi、Huma 均为 MIT | chi(需要路由分组、子路由和较多中间件;兼容标准 http.Handler)Huma(想从代码生成 OpenAPI 并做请求校验(接口约定见 16)) | 为简单服务引入全栈框架:多一层抽象和依赖,收益很小 |
| PostgreSQL 驱动 | pgx/v5 + pgxpool · MIT | database/sql + pgx 的 stdlib 适配(要配合只支持 database/sql 的库或 ORM,或要保持驱动可替换) | — |
| SQL 代码生成 | sqlc(配置 sql_package: "pgx/v5") · sqlc 为 MIT(CLI 只在开发时运行,生成的代码不带运行时依赖)以 SQL 为准、查询基本固定的项目 | 直接用 pgx 手写查询(查询很少,或大量动态 SQL) GORM、Bun、Ent 等 ORM(以增删改查为主、团队已熟悉;要额外关注生成的 SQL) | sqlc、大型 ORM、自研查询封装叠着用:三套写法并存,事务和错误处理各不相同 |
| 数据库迁移工具 | goose · goose、golang-migrate 为 MIT。Atlas 源码仓库为 Apache-2.0,社区版即按此构建,含版本化迁移的 diff、apply、status;官方标准版二进制按 Atlas MSA 授权,迁移 down、checkpoint、迁移 lint、视图和触发器等对象、部分驱动与集成只在标准版或付费的 Pro 中提供 迁移原则(向前修复、受控发布步骤、不在每个实例启动时跑)见 17 | golang-migrate(团队已在用,或只需要一个精简的命令行) Atlas(想要声明式 schema 比对和迁移 lint;先确认需要的功能在哪个版本里) | 在应用启动代码里手写 DDL:多副本同时执行、无记录、无法审查 |
| 测试 | 标准库 testing + go-cmp;集成测试用 testcontainers-go · go-cmp 为 BSD-3-Clause;testcontainers-go 为 MIT;go.uber.org/mock 为 Apache-2.0 | testify(团队偏好断言库;用 require 而不是 assert,失败即停) go.uber.org/mock(接口方法多、手写 fake 成本高) | golang/mock:已归档 |
| goroutine 泄漏检测 | go.uber.org/goleak · MIT | — | — |
| 有限并发 | golang.org/x/sync/errgroup | golang.org/x/sync/semaphore(需要按权重限流) | — |
| 日志 | log/slog | zap(存量项目已在用;新代码可用 slog 接口、zap 作为 Handler) | 标准库 log 的 Printf:非结构化,无级别 |
| 依赖注入 | 在 main 或组合根里手写构造函数 · Fx 为 MIT | Uber Fx(组件多、启动和停止顺序复杂) | google/wire:已归档 |
| 架构检查 | go-arch-lint · go-arch-lint、arch-go 均为 MIT | arch-go(除依赖方向外还要检查函数规模、命名、包内容等规则) depguard(只需禁止导入少数具体包) | — |
go 与 toolchain 行 + 固定补丁版的构建镜像default: all 再加大量 nolinthttp.Handler);Huma(想从代码生成 OpenAPI 并做请求校验(接口约定见 16))sql_package: "pgx/v5") · sqlc 为 MIT(CLI 只在开发时运行,生成的代码不带运行时依赖)go 1.xx,CI 用 golang:latest 镜像构建if err.Error() == "not found"errors.Is(err, ErrNotFound)go func() { … }() 做发邮件、写审计,用的还是请求 ctxcontext.WithoutCancel 加超时http.ListenAndServe(":8080", mux) 直接上线http.Server 并设好各项超时%w 包装往上传,在边界记录一次golangci-lint migrate,再 config verify下面的内容还没核实或待补调研,不要当成规则执行。