con un clic
go-idioms
写 Go 时使用。地道 Go:错误处理、并发、接口的正确姿势。
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
写 Go 时使用。地道 Go:错误处理、并发、接口的正确姿势。
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
做 Cocos Creator 多机型/多分辨率适配时使用。Canvas、Widget、安全区。
Cocos Creator 用 AssetBundle 做分包/远程资源时使用。加载、释放、依赖、缓存。
优化 Cocos Creator 渲染性能时使用。合批、图集、动静分离、Label。
给 Cocos Creator 原生包做热更新时使用。version manifest、增量、校验、回滚。
写 Cocos Creator 动效/动画时使用。tween、Animation、Spine、性能与清理。
做 Cocos Creator 大量条目列表时使用。虚拟列表、节点复用。
| name | go-idioms |
| description | 写 Go 时使用。地道 Go:错误处理、并发、接口的正确姿势。 |
| category | language |
| tags | ["go","golang","惯用法"] |
err、无法退出的 goroutine 或过度抽象的接口时。defer 管理资源或发现循环变量捕获问题时。if err != nil 不忽略;用 %w 包装保留链路规则: 每个返回 error 的调用结果必须检查;向上传递时用 fmt.Errorf("操作说明: %w", err) 包装,保留原始错误供 errors.Is/errors.As 使用;禁止用 _ 丢弃 error。
为什么: AI 在快速生成代码时极易写出 result, _ := db.Query(...)——把错误扔掉,程序继续用一个零值 result 往下跑,最终在几十行后 nil pointer panic,且调用栈完全看不出根因。Go 的设计哲学就是让错误无处可藏,用 _ 丢弃等于主动绕过这层保护。
怎么做:
if err != nil { return ..., fmt.Errorf("xxx: %w", err) }。errors.Is(err, ErrNotFound) 或 errors.As(err, &target);不要字符串匹配。go 启动的 goroutine 要能退出规则: 每个 go func() 启动的 goroutine 都必须有明确的退出路径(通过 context.Done()、关闭 channel 或 WaitGroup);共享内存的并发访问必须用 sync.Mutex/sync.RWMutex 或原子操作保护;不用裸 goroutine 泄漏。
为什么: AI 常见并发 bug:go func() { for { process() } }() 启动后无法停止,服务关闭时 goroutine 还在运行,造成资源泄漏或数据竞争。另一个高频错误是在循环中直接捕获循环变量:go func() { fmt.Println(v) }() —— 所有 goroutine 最终打印同一个 v(Go 1.22 前的经典陷阱,升级版本不等于旧代码变安全)。
怎么做:
ctx context.Context,监听 ctx.Done()。sync.WaitGroup;传结果 → buffered channel,大小与 goroutine 数匹配。go func(v T) { ... }(v)(或升级到 Go 1.22+)。go test -race 在 CI 中检测数据竞争。规则: 接口只包含调用方实际需要的方法(通常 1–3 个);接口定义放在使用它的包,不放在实现包;不提前为"可能有多种实现"预留接口。
为什么: AI 惯用 Java 思维写 Go:在 service 包里定义一个有 15 个方法的 UserService 接口,然后同一个包里只有一个实现。这在 Go 里是反模式——接口越大,满足它的类型越少,测试 mock 越难写。Go 的 io.Reader 只有一个方法,是接口设计的标杆。
怎么做:
-er 结尾(Reader、Closer、Notifier)。defer 释放资源;注意循环变量捕获与 slice 共享底层数组规则: 打开文件/连接后立即 defer f.Close();注意 defer 在循环中会积压到函数返回才执行,循环内的资源要显式关闭或拆成独立函数;对 slice 做 append 或子切片时,理解共享底层数组的副作用。
为什么: 两类经典 AI 错误:①在循环里 defer rows.Close(),以为每次迭代都会关闭,实际上等函数结束才关,数据库连接池被耗尽;②sub := s[1:3],修改 sub 的元素时顺手改了原始 s 的数据,引发难以复现的 bug。
怎么做:
defer,或在每次迭代末尾显式 Close()。copy 出一份,或用 s[1:3:3](三索引切片)限制容量,阻止 append 时共享底层数组被意外扩写。defer 中有可能返回错误(如 f.Close())→ 用命名返回值捕获,不要默默丢掉关闭错误。gofmt 与 Effective Go;不写 Java 风格的 Go规则: 代码必须通过 gofmt(或 goimports)格式化;包名小写单词无下划线;构造函数用 NewXxx;不用 getter/setter 方法封装公开字段(直接暴露字段);错误字符串小写、不加标点。
为什么: AI 极容易把其他语言的风格带进 Go:GetUserName() getter、user_name 下划线命名、errors.New("Database connection failed.") 大写加句号——每一条都违反 Go 官方惯例,过 golangci-lint 都会报警,在 code review 里会被逐条打回。
怎么做:
gofmt -l . 和 golangci-lint run,有 diff 就失败。fmt.Errorf("用户不存在: %w", err) —— 小写开头,不以句号结尾(因为调用方会继续包装)。// 反例 — 错误被丢弃,goroutine 无退出机制
func StartWorker() {
go func() {
for {
rows, _ := db.Query("SELECT ...") // ❌ 忽略 err
process(rows)
// ❌ 永远不会退出,服务关闭时泄漏
}
}()
}
// 正例 — 显式错误处理 + context 控制生命周期
func StartWorker(ctx context.Context, db *sql.DB) error {
go func() {
for {
select {
case <-ctx.Done():
return // ✅ 能干净退出
default:
}
rows, err := db.QueryContext(ctx, "SELECT ...")
if err != nil {
log.Printf("query: %v", err) // ✅ 错误被处理
return
}
process(rows)
rows.Close() // ✅ 循环内显式关闭,不用 defer
}
}()
return nil
}
// 反例 — 实现包里定义了臃肿接口,测试 mock 需要实现全部 15 个方法
package userservice
type UserService interface { // ❌ 15 个方法,放在实现包
GetUser(id int) (*User, error)
CreateUser(u *User) error
UpdateUser(u *User) error
DeleteUser(id int) error
ListUsers() ([]*User, error)
// ... 还有 10 个方法
}
// 正例 — 使用方定义最小接口,测试只需 mock 用到的方法
package orderhandler // ✅ 使用方的包
// 只需要查询用户,定义单方法接口
type UserGetter interface {
GetUser(id int) (*User, error) // ✅ 够用即可
}
func NewHandler(users UserGetter) *Handler { ... }
error 的调用结果都检查了,没有用 _ 丢弃?%w 包装,保留了错误链?go func() 启动的 goroutine 都有通过 context 或 channel 退出的路径?defer 积压到函数末尾?gofmt 格式化,错误字符串是小写且不含句号?