按照上一章的说法,我所有的函数方法都要后面加一个err?
要不然没法一层一层往上传递?
你抓到 Go 语言的"命脉"了!是的,在 Go 赛道,几乎所有涉及 IO、数据库、网络或复杂业务的函数,标配都是返回 (数据, error)。
这确实和 PHP 习惯"直接返回数据,出事抛异常"不一样。在 Go 里,这叫"显式错误处理"。
1. 为什么要一层层往上交?
想象你在 PHP 里:
// PHP 逻辑
function getInfo() {
return $db->query(); // 如果这里崩了,直接炸到顶层,中间的逻辑管不住
}
而在 Go 里,这就是一场接力赛:
- Repo 层:拿到最原始的错,包一下,交给 Logic。
- Logic 层:看看这个错能不能救(比如读缓存),救不了,再包一下上下文,交给 Handler。
- Handler 层:最后拍板,是给用户看"系统维护",还是记录一条"严重报警"。
如果没有每一层后面的 error,这个接力棒就断了,顶层根本不知道底层发生了什么,也就没法写出有意义的日志。
2. 难道所有函数都要加 err 吗?
不一定。你可以根据"有没有可能失败"来判断:
- 需要加
err的(涉及外部环境):- 查数据库、读写文件、调远程接口、解析 JSON、字符串转数字。
- 准则:只要这个函数依赖"运气"(环境、网络、用户输入),就得带
err。
- 不需要加
err的(纯逻辑运算):- 计算两个数之和、拼接两个字符串、从结构体里取一个已经存在的字段。
- 准则:只要这个函数是"确定性"的,给同样的输入一定有同样的输出,就不需要
err。
3. 写起来会不会很累?
刚开始你会觉得 if err != nil 写得手酸,但你会发现这种写法带来的安全感:
- 没有隐藏炸弹:你盯着代码看,哪里有
err哪里就有风险,一目了然,不需要猜哪里会抛异常。 - 强制思考:Go 强制你面对错误。PHP 里你可能会忘了
try-catch,但 Go 里如果你忽略了返回的err,编译器或静态检查工具会跳出来提醒你。
4. 报错链条实战总结
你可以把这套公式背下来:
- 底层 (Repo):
return nil, fmt.Errorf("repo context: %w", err) - 中层 (Logic):
return nil, fmt.Errorf("logic context: %w", err) - 顶层 (Handler):
infra.Log("api").Error("final message", "err", err)
这就是你之前担心的"日志白写了"的终极答案: 底层和中层只负责"攒信息",顶层负责"按开关写日志"。