应显式定义回调类型如type TaskCallback func(result interface{}, err error),固定参数顺序,调用前判空,异步时用带缓冲channel和context控制,并明确发布者与订阅者责任边界。

func本身不是回调,传进去再调用才是——Go 没有“回调语法”,只有函数值传递和显式调用。直接写裸函数签名、不判空、不处理 panic、不控制执行上下文,上线后大概率出问题。
怎么定义回调类型才不容易翻车
别在每个函数参数里重复写 func(string, error) 这种签名。它难读、难改、IDE 跳转失效。
- 用
type显式命名:比如type TaskCallback func(result interface{}, err error) - 必须双参数:
result和err分开,避免把nil当成功或误判失败 - 参数顺序固定:输入数据在前,回调在最后,方便后续加
context.Context或...Option - 别用
interface{}做参数类型——编译期零检查,运行时断言失败就 panic
调用前不检查 nil 就等于埋雷
Go 不会自动跳过 nil 函数值,cb() 直接 panic:invalid memory address or nil pointer dereference。
- 注册时强制判空:
if cb != nil { cb(result, err) },别指望 defer recover 替你兜底 - 闭包捕获变量要小心:循环中传
i,得写成func(i int) { cb(i) }(i),否则所有回调都拿到最后一个值 - 如果回调是用户可配置的(比如插件、YAML 注入),更要提前校验类型兼容性,不能只看非 nil
异步场景下,别直接 go callback() 就完事
裸 go callback(e) 看似异步,实则失控:没缓冲、无超时、不防 panic、不控并发、无法等待。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 带缓冲 channel 是底线:
events := make(chan *Event, 128),缓冲大小按 QPS × 平均延迟预估 - 分发 goroutine 必须唯一:
go func() { for e := range events { dispatch(e) } }(),多个 reader 会竞态 - 每个回调执行前必须绑定子 context:
subCtx, cancel := context.WithTimeout(ctx, 5*time.Second),并在开头检查subCtx.Err() != nil - 用
errgroup.Group启动回调,主流程退出时能eg.Wait()等待全部结束,而不是放任 goroutine 泄漏
真正麻烦的从来不是“怎么调”,而是“调完谁负责”
回调执行失败没人知道,panic 静默吞掉,慢 handler 卡死整个分发循环,channel 缓冲区涨到 OOM —— 这些都不是语法问题,是责任边界没划清。
发布者只该投递,不该感知订阅者是否存在;订阅者自己管理生命周期、缓冲、重试和监控。一旦把 func() 当作通信协议,就等于把错误传播、超时控制、背压策略全推给调用方,而这些恰恰是最容易被忽略的点。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










