Go 错误处理:先检查error,再使用返回结果
实现页大小校验,保留错误原因和操作上下文,并让调用者明确区分成功路径和失败路径。
本节目标
实现页大小校验,保留错误原因和操作上下文,并让调用者明确区分成功路径和失败路径。
前置知识
理解函数的多返回值。认识error是用于传递失败信息的接口,不要求先掌握异常机制。
准备与运行
在一个新的练习目录执行以下命令。目录名可以修改;若该目录已经初始化过模块,不要重复执行go mod init。
mkdir go-lesson-08
cd go-lesson-08
go mod init example.com/devcn/lesson08将下方完整代码保存为当前目录的main.go,然后执行:
go fmt ./...
go run .先用go version确认工具链可用。本例在Go 1.25.7、macOS ARM64环境验证,只使用标准库;没有测试所有操作系统和Go版本。
核心概念
pageSize返回合法的页大小,或返回错误。约定是:err不为nil时,调用方不应把数值结果当成成功数据。程序应先处理失败分支,再继续使用结果。
ErrSize表示“页大小必须为正数”这一类问题。函数返回时用fmt.Errorf补充实际输入,并通过%w保留错误链。调用方用errors.Is判断类别,不需要依赖完整错误文字恰好相等。
错误消息适合帮助人理解上下文,错误类别适合帮助程序选择行为。更改错误说明文字时,不应迫使调用方跟着修改字符串比较逻辑。对于可预期的输入错误,返回error比panic更容易被表单、接口和命令行处理。
完整示例
package main
import (
"errors"
"fmt"
)
var ErrSize = errors.New("size must be positive")
func pageSize(n int) (int, error) {
if n <= 0 {
return 0, fmt.Errorf("page size %d: %w", n, ErrSize)
}
return n, nil
}
func main() {
for _, input := range []int{0, 20} {
n, err := pageSize(input)
if err != nil {
if errors.Is(err, ErrSize) {
fmt.Println("页大小无效")
} else {
fmt.Println("校验失败:", err)
}
continue
}
fmt.Printf("页大小:%d\n", n)
}
}运行后应得到:
页大小无效
页大小:20代码如何工作
main依次校验0和20。第一轮失败,先检查errors.Is,输出可理解的提示,随后continue跳过成功输出。第二轮err为nil,才打印实际生效的页大小20。
这让“先判断错误,再使用结果”的约定直接体现在代码结构里。示例保留了未知错误分支,避免未来校验逻辑增加新错误时被静默吞掉。
实际服务中可以把已知输入错误转成表单提示,把未知错误记录到合适的日志位置,但不要把内部敏感细节直接暴露给所有访问者。
常见错误与边界
- 用下划线丢弃error会让失败路径无法被正确处理。
- errors.Is按错误链识别目标,单纯比较Error()字符串容易受文案变化影响。
- %w用于包装error,不是给任意值增加上下文的通用占位符。
- 不要在每一层重复打印同一个错误;调用链应明确哪一层负责最终记录或展示。
练习
把输入列表改成-1、0和20,检查前两项都进入失败分支,最后一项才输出有效值。
参考答案
将main中的列表替换成[]int{-1, 0, 20},应输出两次“页大小无效”,再输出“页大小:20”。
进一步需要限制最大页大小时,可新增业务上限检查。先决定“超过上限是拒绝还是截断”,再写代码和测试,不要让这个行为隐藏在实现细节里。
文章回复
0 条公开回复