• golang 异常错误处理 error panic recover


    Go 语言通过内置的错误接口提供了非常简单的错误处理机制。

    error类型是一个接口类型,这是它的定义:

    type error interface {
        Error() string
    }

    我们可以在编码中通过实现 error 接口类型来生成错误信息。

    函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:

    func Sqrt(f float64) (float64, error) {
        if f < 0 {
            return 0, errors.New("math: square root of negative number")
        }
        // 实现
    }

    在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:

    result, err:= Sqrt(-1)
    
    if err != nil {
       fmt.Println(err)
    }
    // 定义一个 DivideError 结构
    type DivideError struct {
        dividee int
        divider int
    }
    
    // 实现 `error` 接口
    func (de *DivideError) Error() string {
        strFormat := `
        Cannot proceed, the divider is zero.
        dividee: %d
        divider: 0
    `
        return fmt.Sprintf(strFormat, de.dividee)
    }
    
    // 定义 `int` 类型除法运算的函数
    func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
        if varDivider == 0 {
                dData := DivideError{
                        dividee: varDividee,
                        divider: varDivider,
                }
                errorMsg = dData.Error()
                return
        } else {
                return varDividee / varDivider, ""
        }
    
    }
    
    func main() {
    
        // 正常情况
        if result, errorMsg := Divide(100, 10); errorMsg == "" {
                fmt.Println("100/10 = ", result)
        }
        // 当除数为零的时候会返回错误信息
        if _, errorMsg := Divide(100, 0); errorMsg != "" {
                fmt.Println("errorMsg is: ", errorMsg)
        }
    
    }

    panic官方文档 https://go.dev/ref/spec#Handling_panics

    Handling panics

    Two built-in functions, panic and recover, assist in reporting and handling run-time panics and program-defined error conditions.

    func panic(interface{})
    func recover() interface{}
    

    While executing a function F, an explicit call to panic or a run-time panic terminates the execution of F. Any functions deferred by F are then executed as usual. Next, any deferred functions run by F's caller are run, and so on up to any deferred by the top-level function in the executing goroutine. At that point, the program is terminated and the error condition is reported, including the value of the argument to panic. This termination sequence is called panicking.

    panic(42)
    panic("unreachable")
    panic(Error("cannot parse"))
    

    The recover function allows a program to manage behavior of a panicking goroutine. Suppose a function G defers a function D that calls recover and a panic occurs in a function on the same goroutine in which G is executing. When the running of deferred functions reaches D, the return value of D's call to recover will be the value passed to the call of panic. If D returns normally, without starting a new panic, the panicking sequence stops. In that case, the state of functions called between G and the call to panic is discarded, and normal execution resumes. Any functions deferred by G before D are then run and G's execution terminates by returning to its caller.

    The return value of recover is nil if any of the following conditions holds:

    • panic's argument was nil;
    • the goroutine is not panicking;
    • recover was not called directly by a deferred function.

    The protect function in the example below invokes the function argument g and protects callers from run-time panics raised by g.

    func protect(g func()) {
    	defer func() {
    		log.Println("done")  // Println executes normally even if there is a panic
    		if x := recover(); x != nil {
    			log.Printf("run time panic: %v", x)
    		}
    	}()
    	log.Println("start")
    	g()
    }


    
    

    Golang panic用法

    Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱。因为开发者很容易滥用异常,甚至一个小小的错误都抛出一个异常。在Go语言中,使用多值返回来返回错误。不要用异常代替错误,更不要用来控制流程。在极个别的情况下,也就是说,遇到真正的异常的情况下(比如除数为 0了)。才使用Go中引入的Exception处理:defer, panic, recover。

    这几个异常的使用场景可以这么简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。

    package main
    
    import "fmt"
    
    func main(){
        fmt.Println("c")
         defer func(){ // 必须要先声明defer,否则不能捕获到panic异常
            fmt.Println("d")
            if err:=recover();err!=nil{
                fmt.Println(err) // 这里的err其实就是panic传入的内容,55
            }
            fmt.Println("e")
        }()
    
        f() //开始调用f
        fmt.Println("f") //这里开始下面代码不会再执行
    }
    
    func f(){
        fmt.Println("a")
        panic("异常信息")
        fmt.Println("b") //这里开始下面代码不会再执行
        fmt.Println("f")
    }
    
    
    输出结果:
    
    c
    a
    d
    异常信息
    e
    
    • 内建函数
    • 假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
    比如panic函数内有:
        defer   函数1
        defer   函数2
        defer   函数3
    那么执行顺序就是:
        函数3
        函数2
        函数1
    
    • 返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行,这里的defer 有点类似 try-catch-finally 中的 finally
    • 到goroutine整个退出,并报告错误

    recover:

    • 内建函数
    • 用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为
    • 一般的调用建议(如上面的例子)
      a). 在defer函数中,通过recever来终止一个gojroutine的panicking过程,从而恢复正常代码的执行
      b). 可以获取通过panic传递的error
      简单来讲:go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。

    注意:利用recover处理panic指令,defer必须在panic之前声明,否则当panic时,recover无法捕获到panic.

    应用示例:

    比如go编译器的源码

    注释: 编译器是构建正在运行的二进制文件的编译器工具链的名称。已知的工具链为:
    gc Also known as cmd/compile.
    gccgo The gccgo front end, part of the GCC compiler suite.

     

     

    介绍 错误和异常 (errors and exceptions)

    大多数编程语言支持 exception 作为处理 error 的标准方式,比如 Java,Python。虽然方便,但也会带来许多问题,这就是为什么他们不喜欢其他语言或者风格。对 exception 的主要吐槽点是它们为控制流引入了 "side channel",当阅读代码的时候,你必须时刻记住这个 exception 引发的流程控制方向。这也导致某些代码阅读起来比较困难[1]。

    让我们开始具体谈谈 Go 中的错误处理。我假定你知道 Go 中错误处理的“标准”方式。下面如何打开文件的代码:

    f, err := os.Open("file.txt")
    if err != nil {
        // handle error here
    }
    // do stuff with f here
    
    

    如果文件不存在, os.Open() 函数将返回一个非空 error ,在其他语言中这样的错误处理是完全不同的,比如 Python 中的内建 open() 函数将在错误发生时抛出异常。

    try:
        with open("file.txt") as f:
            # do stuff with f here
    except OSError as err:
        # handle exception err here
    

    Python 始终坚持通过 exception 来处理 error。因为这种无处不在的错误处理方式导致经常被吐槽。甚至利用 exception 作为序列结束的信号。到底 exception 的真正含义是什么?以下是来自 Rob Pike 在邮件中 对此的贴切阐述,其中塑造了现有的 Go panic/recover 机制雏形。

    这正是提案试图避免的那种事情。 Panic 和 recover 不是通常意义的异常机制。通常的方式是将 exception 和一个控制结构相关联,鼓励细粒度的 exception 处理,导致代码往往不易阅读。在 error 和调用一个 panic 之间确实存在差异,而且我们希望这个差异很重要。在 Java 中打开一个文件会抛出异常。在我的经验中,打开文件失败是最平常不过的事。而且还需要我写许多代码来处理这样的 exception。

    客观的讲。exception 的支持者嘲笑 Go 的这种过于明确的 error 处理有多方面的原因。首先,请注意上面两个例子中代码的顺序。 在 Python 中,程序的主要流程紧跟在 open 调用之后,并且错误处理被委托给后一阶段(更不用说在许多情况下,异常将被堆栈中更上一级的函数捕获到而不是在此函数中)。 另一方面,在 Go 中,立刻处理错误这种方式,可能会使主程序流程混淆。 此外,Go 的错误处理非常冗长 - 这是该语言的主要吐槽点之一。 我将在后面提到一种可能的方法来解决这个问题。

    除了上面 Rob 的引用之外,在 FAQ 中总结了 Go 的 exception 哲学。

    我们认为将异常耦合到控制结构(如 try-catch-finally 惯用语)会导致代码错综复杂。 它还倾向于鼓励程序员标记太多普通错误,例如打开文件失败。

    然而,在某些情况下,具有类似异常的机制实际上是有用的 ; 像 Go 这样的高级语言甚至是必不可少的。 这就是存在 panic 和 recover 的原因。

    偶尔的 panic 是必要的

    Go 是一种安全的语言,运行时检查一些严重的编程错误。例如在你访问超出 slice 边界的元素时,这种行为是未定义的,因此 Go 会在运行时 panic。例如下面的小程序。

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        s := make([]string, 3)
        fmt.Println(s[5])
    }
    

    程序将终止于一个运行时 error。

    panic: runtime error: index out of range
    
    goroutine 1 [running]:
    main.main()
        /tmp/sandbox209906601/main.go:9 +0x40
    

    其他一些会引发 panic 的就是通过值为 nil 的指针访问结构体的字段,关闭已经关闭的 channel 等。怎样选择性的 panic ?可以通过访问 slice 时返回 result,error 两个值的方式实现。也可以将 slice 的元素赋值给一个可能返回 error 的函数,但是这样会将代码变复杂。想象一下,写一个小片段,foo,bar,baz 都只是一个字符串的一个 slice,实现片段之间的拼接。

    foo[i] = bar[i] + baz[i]
    
    

    就会变成下面这样冗长的代码:

    br, err := bar[i]
    if err != nil {
        return err
    }
    bz, err := baz[i]
    if err != nil {
        return err
    }
    err := assign_slice_element(foo, i, br + bz)
    if err != nil {
        return err
     }
    

    这不是开玩笑,不同语言处理这样的方式是不一样的。如果 slices/lists/arrays 的指针 i 越界了,在 Python 和 Java 中就会抛出异常。C 中没有越界检查,所以你就可以尽情的蹂躏边界外的内存空间,最后将导致程序崩溃或者暴露安全漏洞。C++ 中将采用折中的处理方式。性能优先的模块采用这种不安全的 C 模式,其他模块(比如 std::vector::at)采用抛出异常的方式。

    因为上面重写的小片段变得如此冗长是不可接受的。Go 选择了 panic ,这是一种类似异常的机制,在代码中保留了像 bugs 这样最原始的异常条件。

    这不只是内建代码能够这样用,自定义代码也可以在任何需要的地方调用 panic。在有些可能导致可怕错误的地方还鼓励使用 panic 抛出 error,比如 bug 或者一些关键因素被违反的时候。比如在 swich 的某个 case 在当前上下文中是不可能发生的,在这种 case 中只有一个 panic 函数。这无形中等价于 Python 中的 raise 或者 C++ 中的 throw。这也强有力的证明了在捕获异常方面 Go 的异常处理的特殊之处。

    Image

     

    Go 中 panic 恢复的限制条件

    讨论

    一般就HTTP服务的全局做一个recover,然后任何地方panic都能全局捕获相应一个错误

     

    panic简单示例

     

    package main
    import "fmt"
    func main() {
        defer func() {
            if info := recover(); info != nil {
                fmt.Println("触发了宕机", info)
            } else {
                fmt.Println("程序正常退出")
            }
        }()
        fmt.Println("bbbbbb")
        fmt.Println("cccccc")
        panic("fatal error")
        fmt.Println("ddddd")
        defer func() {
            fmt.Println("eeeeeeee")
        }()
    }

     

     参考:https://zhuanlan.zhihu.com/p/373653492

    errors.New函数根据传入的字符串返回一个error类型。

    语法和参数

    需导入Go原生包errors

    import errors

    函数签名: 

    func New(text string) error


    func division(number1, number2 int) (float64, error) {
    if number2 == 0 {
    return 0.0, errors.New("division by zero")
    }

    return float64(number1) / float64(number2), nil
    }



  • 相关阅读:
    HDU ACM 1020 Encoding
    HDU ACM 1019 Least Common Multiple
    HDU ACM 1009 FatMouse' Trade
    HDU ACM 1032 The 3n + 1 problem
    HD ACM 1061 Rightmost Digit
    UVa 401 Palindromes
    UVa 489 Hangman Judge
    HDU ACM 1071 The area
    5/25
    受涼6/8
  • 原文地址:https://www.cnblogs.com/youxin/p/16026694.html
Copyright © 2020-2023  润新知