DevCN
菜单
全部文章快讯开发科技深度热点

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”。

进一步需要限制最大页大小时,可新增业务上限检查。先决定“超过上限是拒绝还是截断”,再写代码和测试,不要让这个行为隐藏在实现细节里。

DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。