GO错误处理【4】报错即链条

上文你提到了"报错即链条",能不能清楚描述一下什么意思,Go的return err和Java/PHP的try-catch为什么要搞这么大差别?

这个弯儿确实得绕一下。Java/PHP 的异常是"横冲直撞"的,而 Go 的错误是"接力棒"。

我用一个"楼下保安抓小偷"的故事,一次性给你讲透"包装"和"链条":

1. 场景:数据库连接断了(小偷进屋了)

第一步:Repo 层(发现异常的现场保安)

保安发现门锁被撬了,但他不知道小偷想干嘛(是偷钱还是偷资料?)。他不能自己在那喊,他得汇报。

  • 做法:把现场情况"包装"一下,往上传。

    // internal/repo/user.go
    func (r *UserRepo) GetByID(id int) (*User, error) {
    err := r.pool.QueryRow(...)
    if err != nil {
    // 【错误包装】:%w 就像是在证据袋里装了原件,外面贴了张纸条说明
    return nil, fmt.Errorf("数据库底层报错 (ID:%d): %w", id, err)
    }
    return user, nil
    }

第二步:Logic 层(保安队长)

队长接到消息,他知道这个 ID 是"大客户"。他需要增加业务信息,继续往上传。

  • 做法:不打日志,继续包装。

    // internal/logic/user.go
    func (l *UserLogic) UserProfile(id int) (*User, error) {
    user, err := l.repo.GetByID(id)
    if err != nil {
    // 【再次包装】:叠罗汉,加上业务层的描述
    return nil, fmt.Errorf("查询大客户资料失败: %w", err)
    }
    return user, nil
    }

第三步:Middleware/Handler(总指挥中心)

终于到了处理问题的地方了。这里是链条的终点。

  • 做法:记录日志,并告诉前端(报信)。

    // internal/handler/user.go
    func GetUserHandler(w http.ResponseWriter, r *http.Request) {
    user, err := logic.UserProfile(123)
    if err != nil {
    // 【终结错误】:在这里,我们才真正调用 infra.Log
    // 因为 err 里包含了刚才每一层包装的信息,打印出来长这样:
    // "查询大客户资料失败: 数据库底层报错 (ID:123): connection refused"
    infra.Log("api").Error("请求失败", "err", err.Error())

    复制代码
          res.ApiError(w, 500, 10001, "系统忙") // 给用户的只有一句话
          return
      }
      res.ApiSuccess(w, user)

    }


2. 为什么这样是"最优解"?

概念 形象比喻 实际作用
错误包装 (%w) 叠罗汉 让错误在每一层都带上"当时在干嘛"的信息,而不是只有一个干巴巴的 connection refused
报错即链条 接力棒 保证一个错误只在终点打一次日志。避免你的日志文件里,同一个错误在 Repo 出现一次、Logic 出现一次、Handler 又出现一次。

3. "包装"的终极威力

当你在日志里看到一串:
查询大客户资料失败: 数据库底层报错 (ID:123): connection refused

你会瞬间明白:

  1. 最底层:网络不通(connection refused)。
  2. 中间层:是在查 ID 为 123 的时候崩的。
  3. 业务层:这是大客户模块的问题,得赶紧修!

4. 总结你的疑惑

  • 问:日志那里不是白写了?
    • 答:没白写!日志函数 (infra.Log) 是"笔",包装 (%w) 是"墨"。你以前是在每张纸上写一个字(到处打日志),现在是把所有信息聚成一句话,最后在这张纸上写个大的(只在终点打一次)。
  • 问:我要再写一次这个?
    • 答:不需要。你只需要在 Repo 和 Logic 里写 fmt.Errorf,只在 Middleware 或 Handler 里写一次 Log().Error()
相关推荐
审判长烧鸡10 小时前
【Go工具】go-playground是什么组织?官方的?
开发语言·安全·go
别样的感动11 小时前
我写了一个 Go 框架:用 DSL 替代 ORM,代码体积减半,开发效率翻倍
go
明月_清风15 小时前
Go语言空接口与类型断言完全指南:从"万能容器"到"类型还原"
后端·go
蓝宝石的傻话18 小时前
security-collector-exporter:用Prometheus 解决 Linux 的安全审计
go
tyung19 小时前
Go 手写二叉堆优先队列:避开 container/heap 的性能陷阱
数据结构·后端·go
审判长烧鸡1 天前
【PHPer转Go】fmt vs log/slog
go·php
漓漾li2 天前
每日面试题(2026-05-20)- GO AI agent全栈
后端·架构·go
.魚肉2 天前
Raft 共识算法 · 演示系统(多终端)
算法·go·raft·分布式系统
审判长烧鸡2 天前
【Go工具】go-playground除了validator还有哪些常用的库
go·web
审判长烧鸡2 天前
Go 新版核心知识点合集(适配 Go1.18+ 含泛型 + 断言 + 接口 + 指针接收者全套)
go