go错误处理应显式命名并提前返回,避免嵌套if;优先用自定义错误类型替代字符串拼接;禁止忽略或log.fatal;多错误用errors.join,注重错误可扩展性。

错误值应该明确命名并提前返回,而不是嵌套在 if 里
Go 的错误处理不是靠 try/catch,而是靠显式检查 err 并立即处理。很多人写成这样:
if err := doSomething(); err != nil {
// 处理 err
return err
}
看起来简洁,但实际掩盖了错误来源,也容易漏掉对 err 的二次使用(比如日志里没打具体值)。更清晰的做法是分开声明、显式判断:
- 用具名变量接收错误:
err := doSomething(),再单独写if err != nil - 立刻
return或panic,不继续执行后续逻辑(除非你真需要“尽力而为”) - 避免把多个操作塞进一个 if 条件里,比如
if f, err := os.Open(...); err != nil—— 这会让f作用域受限,后续没法 defer 关闭
自定义错误类型比字符串拼接更利于排查和测试
直接用 fmt.Errorf("failed to parse %s: %w", input, err) 没问题,但一旦需要区分错误类型(比如重试、跳过、告警),字符串匹配就不可靠。这时候该用自定义错误:
type ParseError struct {
Input string
Err error
}
func (e *ParseError) Error() string {
return fmt.Sprintf("parse error on %q: %v", e.Input, e.Err)
}
func (e *ParseError) Unwrap() error { return e.Err }
- 实现
Unwrap()方法,支持errors.Is()和errors.As() - 字段暴露关键上下文(如失败的
Input、原始Err),方便日志和调试 - 测试时可用
errors.As(err, &want)精确断言错误类型,而不是模糊匹配字符串
不要忽略错误,也不要盲目 log.Fatal
log.Fatal 在库代码里是反模式——它会终止整个进程,调用方无法控制恢复逻辑。而 _ = someFunc() 更危险,等于主动丢弃故障信号。
立即学习“go语言免费学习笔记(深入)”;
- 所有导出函数返回的
error,调用方必须显式处理:返回、重试、转换、记录后忽略(需加注释说明为什么可忽略) - 库函数不应调用
log.Fatal或os.Exit;CLI 工具主函数可以,但要限制在main()最外层 - 临时调试用的
fmt.Printf("DEBUG: %v\n", err)必须删掉或换成log.Debug(配合日志等级开关)
errors.Join 和 errors.Is 配合使用能简化多错误场景
当一个操作可能触发多个子错误(比如并发请求一批 URL),以前常靠切片收集再拼字符串,现在可以用 errors.Join:
var errs []error
for _, u := range urls {
if err := fetch(u); err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
调用方就能统一用 errors.Is(err, context.Canceled) 判断是否含取消错误,不用遍历切片。
-
errors.Join返回的错误仍可被errors.Is/errors.As检查,语义清晰 - 注意它不会自动去重,重复添加同一错误会导致多次匹配
- 如果要保留顺序或额外元信息(如哪个 URL 失败),还是得自己封装结构体
fmt.Errorf 很快,明天加个重试策略或监控埋点,就得全量改错误构造方式。一开始就预留自定义错误接口,比后期补救成本低得多。










