浪白的博客の小站
首页项目归档照片墙音乐灵境说说杂谈友链关于
封面

Go 的 error 处理,用久了反而觉得还行

写作时间:2026-04-19 22:15:00
# Go
# 工程实践

"if err != nil" 大概是 Go 被吐槽最多的一句话。我刚开始也吐槽,写了半年之后想法变了,记录一下。

一开始的抵触

看这个:

f, err := os.Open(name)
if err != nil {
    return err
}
defer f.Close()

data, err := io.ReadAll(f)
if err != nil {
    return err
}

六个有效行里有四行在检查错误。第一反应是这语言怎么这么啰嗦,try-catch 一行就能解决的事。

转变的契机是一次线上故障

有个批量任务跑了三个月都没问题,某天开始偶尔丢数据,一天丢几条,量不大但持续。

排查了很久,最后定位到一个第三方 SDK 的方法。它内部做了三件事:发 HTTP 请求、解析响应、写缓存。前两步失败会返回错误,第三步失败会被它自己吞掉——那段代码大致是这样(反编译看到的,不完全是原文):

try {
    $this->cache->set($key, $value, 300);
} catch (\Throwable $e) {
    // 缓存失败不影响主流程
}

注释写得很有道理。但问题是这个"缓存"其实是业务状态的一部分,不是真的缓存。写不进去,下游就认为这一步没执行过。

如果这段代码是 Go 写的,那它必须显式写一个 _ = cache.Set(...) 或者处理错误。那个空白的下划线会让人在 code review 时多问一句"这里为什么忽略"。

PHP 里 catch { } 什么都不做太自然了,自然到没人会注意到。

显式处理带来的三个实际好处

一、错误路径和正常路径写在一起。

PHP 里异常可能从任意一行冒出来,看代码的时候得在脑子里模拟"这行如果抛了会到哪"。Go 里错误就在那行下面,逃不掉。

if err := db.Save(order); err != nil {
    return fmt.Errorf("保存订单 %s: %w", order.ID, err)
}
if err := mq.Publish(evt); err != nil {
    // 消息发不出去,但订单已经存了
    // 这时候要不要回滚?这个决策在 Go 里必须当场做
    return fmt.Errorf("订单 %s 已保存但消息投递失败: %w", order.ID, err)
}

第二段这种部分成功的情况,是最容易出 bug 的地方。try-catch 的写法很容易把两件事包在一个 try 里,出事了整段回滚,反而把已经成功的操作也带走了。

二、错误信息可以带上上下文。

if err != nil {
    return fmt.Errorf("处理用户 %d 的订单 %s: %w", uid, oid, err)
}

这样一路包上去,最后日志里是完整路径:同步任务: 处理用户 123 的订单 A456: 连接数据库超时。

比异常栈好读,因为它只说业务上下文,不掺十几层框架内部调用。

三、errors.Is 和 errors.As 让上层能精确判断。

if errors.Is(err, os.ErrNotExist) {
    return defaultConfig, nil   // 配置不存在就用默认值,不算错
}

PHP 里判断异常类型得 catch (FileNotFoundException $e),而实际抛出来的往往是包装过的 RuntimeException,猜不准。

但确实有些地方还是烦

错误包装容易套娃。 每层都 fmt.Errorf("xxx: %w", err),最后消息变成"调用A: 调用B: 调用C: 调用D: 超时"。得有点纪律,只在能提供新信息的层包装。

重复的检查代码没法消掉。 有人写过用泛型封装的方案:

func Must[T any](v T, err error) T {
    if err != nil { panic(err) }
    return v
}

只适合初始化和测试,业务里用会失去错误处理的意义。

没有错误堆栈。 得自己引 github.com/pkg/errors 或者用 runtime/debug.Stack()。Go 官方在这件事上态度一直很明确:不打算加。

小结

我的结论是:Go 的 error 处理把成本从"写代码时"挪到了"读代码和排查故障时"。

写的时候确实更累,但线上出问题的概率小了很多,出了也更好查。对于要长期维护的服务,这个交换我觉得划算。

当然如果是写个一次性的脚本处理数据,那确实烦。

avatar

浪白

邪恶的浪白

RECOMMENDED

Composer 用了这么多年,几个我认为值得坚持的习惯

2026-04-27 19:15:00

context 传参这件事,我踩过的三个坑

2026-05-06 20:05:00

用 Go 写了个日志清理小工具

2026-06-18 21:10:00

Table of Contents