当前位置: 首页 > article >正文

17 go语言(golang) - 错误处理

错误处理

错误处理是编程中用于识别、响应和恢复程序运行时出现的错误和异常情况的过程。其目的是确保程序的鲁棒性(一个系统、模型或函数在面对错误输入、工作压力、意外情况或故意攻击时仍能保持稳定性和可靠性的能力),即使在出现错误的情况下也能正常运行或优雅地终止。

而Go语言中的错误处理是一种显式的、基于值的机制,与许多其他编程语言使用异常(exception)不同。Go通过返回值来处理错误,这使得代码更加清晰和可预测。

基本概念

error接口

  • Go内置了一个名为error的接口,用于表示错误。源码:

    // The error built-in interface type is the conventional interface for
    // representing an error condition, with the nil value representing no error.
    // 内置的error接口类型是表示错误条件的常规接口,其nil值代表没有错误。
    type error interface {
    	Error() string
    }
    
  • 任何实现了这个接口的方法都可以作为一个错误对象。

自定义error

  • 使用errors.Newfmt.Errorf()创建简单的文本描述性错误。

    func Test1(t *testing.T) {
    	err1 := errors.New("自定义错误1")
    	fmt.Println(err1.Error())
    
    	err2 := fmt.Errorf("自定义错误2")
    	fmt.Println(err2)
    }
    

    输出

    自定义错误1
    自定义错误2
    
  • 如果需要更复杂或结构化的信息,可以定义自己的类型并实现Error()方法。

    type MyError struct {
    	code int
    	msg  string
    }
    
    func (m *MyError) Error() string {
    	return fmt.Sprintf("code:%d,msg:%s", m.code, m.msg)
    }
    
    func Test2(t *testing.T) {
    	myError := MyError{
    		404,
    		"自定义错误!",
    	}
    	fmt.Println(myError.Error())
    	fmt.Println(myError)
    
    }
    

    输出

    code:404,msg:自定义错误!
    {404 自定义错误!}
    

函数返回值

  • 在Go中,函数通常会返回两个值:结果和一个表示可能发生的错误。

    func testDivide(a float64, b float64) (float64, error) {
    
    	if b == 0 {
    		return 0, fmt.Errorf("分母不能为0")
    	} else {
    		return a / b, nil
    	}
    
    }
    

检查和处理错误

  • 调用函数时,需要检查第二个返回值是否为非nil,以判断是否发生了错误。
func Test3(t *testing.T) {
	var a float64 = 1
	var b float64

	divide, err := testDivide(a, b)
	if err != nil {
		fmt.Println(divide, err.Error())
	} else {
		fmt.Printf("结果为:%.2f\n", divide)
	}

}

输出

0 分母不能为0

错误包装与解包

  • Go1.13引入了对error wrapping的支持,通过使用 fmt.Errorf()%w 格式化动词,可以将上下文信息附加到原始错误上。

  • 使用 errors.Unwrap() 可以提取被包装过的一层的错误。

  • errors.Is函数用于检查一个错误是否与目标错误相同,或者是否是目标错误的一部分。

func Test4(t *testing.T) {
	err1 := errors.New("第一层错误")
	err2 := fmt.Errorf("第二层:%w", err1)

	fmt.Println(err1)
	fmt.Println(err2)

	// errors.Is函数用于检查一个错误是否与目标错误相同,或者是否是目标错误的一部分。

	// 注意参数顺序,检查第一个错误是不是第二个错误的子错误,或是否同一个错误
	bool1 := errors.Is(err1, err2) // 返回false
	fmt.Println(bool1)
	bool2 := errors.Is(err2, err1)
	fmt.Println(bool2)

	err3 := errors.Unwrap(err2)
	fmt.Println(err3)
}

输出:

第一层错误
第二层:第一层错误
false
true
第一层错误

错误类型断言与比较

  • 使用类型断言来判断特定类型的自定义异常,并访问其内部字段。
  • (推荐)errors.As函数用于检查一个错误链中是否存在某个特定类型的错误,并且可以将该错误赋值给目标变量。它不仅适用于简单的类型断言,还能处理被包装过的复杂错误结构。
func Test5(t *testing.T) {
	myError := MyError{
		code: 400,
		msg:  "自定义错误",
	}
	var myErr error = &myError

	// 假设我们只知道这是个错误,但不知道具体的类型时,可以这样处理
	if err, ok := myErr.(*MyError); ok {
		fmt.Println(err.code, err.msg)
	}

	// 但是官方还是推荐我们使用as来判断具体的错误类型
	var targetErr *MyError
	if errors.As(myErr, &targetErr) {
		fmt.Println(targetErr.code, targetErr.msg)
	}

}

输出

400 自定义错误
400 自定义错误

断言只能判断是否那个类型,而error.As能查找是否该类型或子类型

func Test6(t *testing.T) {
	myError := MyError{
		code: 400,
		msg:  "自定义错误",
	}

	var myErr error = &myError
	// 再封装一层
	var myErrNew = fmt.Errorf("封装多一层:%w", myErr)

	// 假设我们只知道这是个错误,但不知道具体的类型时,可以这样处理
	if err, ok := myErrNew.(*MyError); ok {
		fmt.Println("1:", err.code, err.msg)
	}

	// 但是官方还是推荐我们使用as来判断具体的错误类型
	var targetErr *MyError
	if errors.As(myErrNew, &targetErr) {
		fmt.Println("2:", targetErr.code, targetErr.msg)
	}

}

输出

2: 400 自定义错误

panic和recover

panic

panic是Go语言中的一种内建机制,用于表示程序遇到了无法恢复的严重错误,导致程序的正常执行流程被中断。它类似于其他编程语言中的异常,但在Go中主要用于不可恢复的错误情况。虽然不推荐在正常业务逻辑中使用 panic,但在一些无法恢复或者必须立即停止程序执行情况下可以考虑,比如数组越界等。

关键特点

  1. 立即停止控制流

    • panic被调用时,程序会立即停止当前函数的执行,并开始运行任何已注册的defer函数。
  2. 向上传播

    • panic会沿着调用栈向上传播,逐层退出每个调用函数,并在每个退出点运行相应的defer语句。
  3. 终止程序

    • 如果没有捕获到并处理这个panic(即没有使用recover),最终会导致整个程序崩溃并打印出堆栈跟踪信息。

使用场景

  • 通常用于那些不应该发生、且无法继续安全运行下去的问题,例如数组越界、空指针解引用等。
  • 不建议在普通业务逻辑中使用,而是保留给真正需要立即停止执行的问题。
func Test1(t *testing.T) {
	defer fmt.Println("执行defer")

	fmt.Println("程序正常执行")

	panic("遇到错误!")

	fmt.Println("程序继续执行")

}

输出

=== RUN   Test1
程序正常执行
执行defer
--- FAIL: Test1 (0.00s)
panic: 遇到错误! [recovered]
	panic: 遇到错误!

goroutine 6 [running]:
testing.tRunner.func1.2({0xa25ade0, 0xa284890})
	...
	...
	...

recover

recover是Go语言中用于处理panic的一种机制。它允许程序在发生panic后恢复执行,从而避免程序崩溃。通常与defer一起使用,以确保即使在函数发生恐慌时也能执行一些清理操作。

基本用法

  • 捕获panic(恐慌):当一个函数调用了 panic() 时,程序会立即停止当前函数的执行,并开始沿着调用栈向上传播,直到遇到一个能够处理该panic的地方。如果在传播过程中遇到了一个包含 recover() 的延迟(deferred)函数调用,那么可以通过 recover() 捕获这个恐慌。
  • 返回值:如果成功捕获到一个正在传播中的恐慌,recover() 会返回传递给该 panic() 调用的值;如果没有处于panic状态,则返回 nil。

使用场景

  • 防止程序崩溃:通过捕获和处理不可预见或致命错误,使得程序能够继续运行或进行适当降级。
  • 清理资源:确保无论是否出现错误,都能正确释放资源(如文件句柄、网络连接等)。
func Test2(t *testing.T) {

	defer func() {
		a := recover()
		if a != nil {
			fmt.Println("recover捕获到panic:", a)
		} else {
			fmt.Println("recover没有捕获到panic")
		}
	}()

	fmt.Println("程序正常执行")

	panic("遇到错误!")

	fmt.Println("程序继续执行")

}

输出:

程序正常执行
recover捕获到panic: 遇到错误!

和throw相似

学过其他语言特别是java的同学应该发现,这与Java中的throw关键字非常相似。

尽管panicthrow在概念上相似,但它们在各自的语言中有着不同的使用习惯和语义。在Go中,panic通常用于处理真正的异常情况,比如程序的内部错误,而错误处理则通过返回error类型来完成。在Java中,throw关键字则用于更广泛的场景,包括业务逻辑中预期的异常情况。

  1. 主动抛出异常:在Go中使用panic和在Java中使用throw都可以主动抛出一个异常或错误条件。

  2. 改变程序控制流:两者都会导致程序的正常控制流被改变。在Go中,panic会立即停止当前函数的执行,并开始执行defer语句,然后向上返回,直到被recover捕获或者程序终止。而在Java中,如果没有在该方法内部捕获,则需要由上层调用者负责处理,否则可能会导致程序崩溃。

  3. 异常传播:在两个语言中,如果抛出的异常没有被立即捕获,它会继续向上传播到调用栈中的更高层,直到被捕获或者导致程序终止。

  4. 用于不可恢复的错误panicthrow都用于表示那些不应该被忽略的严重错误,它们通常用于指示程序遇到了不可恢复的状态。

  5. 停止执行后续代码:在panicthrow之后的代码将不会被执行,直到异常被处理。

  6. 堆栈跟踪:当panic发生时,Go会打印出堆栈跟踪信息,类似于Java中throw异常时的行为,这有助于开发者定位错误发生的位置。


http://www.kler.cn/a/419879.html

相关文章:

  • 威联通-001 手机相册备份
  • Linux下,用ufw实现端口关闭、流量控制(二)
  • 使用Python和OpenCV自动检测并去除图像中的字幕
  • idea 自动导包,并且禁止自动导 *(java.io.*)
  • 【设计模式系列】备忘录模式(十九)
  • C/C++基础知识复习(36)
  • 【实战】Oracle基础之控制文件内容的5种查询方法
  • com.github.gavlyukovskiy依赖是做什么的呢?
  • 关于单片机的原理与应用!
  • DJ秀 4.4.9 | 去除广告专业DJ音乐播放
  • python学习笔记2
  • WPF指示灯的实现方式
  • 【IntelliJ IDEA 中 Run Dashboard 不显示端口号问题解决办法】
  • 基于 echart+ redis 的刷题日历项目设计与实现
  • list(概念和简单应用)
  • 【leetcode100】矩阵置零
  • 23种设计模式之组合设计模式
  • CTF-PWN: 全保护下格式化字符串利用 [第一届“吾杯”网络安全技能大赛 如果能重来] 赛后学习
  • 题海拾贝——环状序列(ACM/ICPC Seoul 2004,UVa1584)
  • 代码设计:设计模式:应对变化
  • Vue:使用 KeepAlive 缓存切换掉的 component
  • 【机器学习】机器学习学习笔记 - 无监督学习 - k-means/均值漂移聚类/凝聚层次聚类/近邻传播聚类 - 05
  • 【JavaScript】下拉框的实现
  • leetcode530:二叉搜索树的最小绝对值差
  • GitHub Copilot革命性更新:整合顶尖AI模型,如何重塑开发体验?
  • 用 React 编写一个笔记应用程序