22

Golang学习笔记之错误处理error、panic (抛出错误),recover(捕获错误)

 5 years ago
source link: https://studygolang.com/articles/16826?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、error

错误表示程序中出现了异常情况。Go 语言通过内置的错误接口提供了非常简单的错误处理机制。

• error类型是go语言的一种内置类型,使用的时候不用特定去import因为它本质上是一个接口

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

type error interface {
    Error() string
}

(1)一个例子理解error

package main
import (
    "fmt"
    "os"
)
func main() {
    //试图打开一个并不存在的文件,这将会返回一个error
    f, err := os.Open("/test.txt")
    if err != nil {
        fmt.Println(err) //no such file or directory
        return
    }
    fmt.Println(f.Name(), "opened successfully")
}

在go中处理错误的惯用方式是将返回的错误与nil进行比较。零值表示没有发生错误,而非零值表示存在错误。

(2)错误定制

上面也看到了error 有了一个签名为 Error() string 的方法。所有实现该接口的类型都可以当作一个错误类型。

第一、通过errors包去订制error

函数原型: func New(text string) error
使用字符串创建一个错误可以认为是New(fmt.Sprintf(...))。

import  "errors"    //使用errors必须import "errors"包
error := errors.New("Myerror")
if error != nil {
    fmt.Print(err)    //Myerror
}

demo

package main
import (
    "errors"
    "fmt"
    "math"
)
func circleArea(radius float64) (float64, error) {
    if radius < 0 {
        //使用字符串创建一个错误
        return 0, errors.New("Area calculation failed, radius is less than zero")
    }
    return math.Pi * radius * radius, nil
}
func main() {
    radius := -20.0
    area, err := circleArea(radius)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("Area of circle %0.2f", area)
}

第二种、通过fmt.Errorf()去订制

函数原型: func Errorf(format string, a ...interface{}) error
Errorf根据format参数生成格式化字符串并返回一个包含该字符串的错误。

err := fmt.Errorf("error")
if err != nil {
    fmt.Print(err)
}

就不贴demo了

只需要把circleArea里if语句的返回值改为

return 0, fmt.Errorf("Area calculation failed, radius %.2f is less than zero",radius)

第三种、使用结构体和字段来定制

type MyError struct {
err error 
}
//订制Error()
func (e MyError) Error() string {
    return e.err.Error()
}
func main() {
   err:=MyError{
        errors.New("error"),
   }
   fmt.Println(err.Error())
}

demo

package main
import (
    "fmt"
    "math"

)
type areaError struct {
    err    string
    radius float64
}
func (e *areaError) Error() string {
    return fmt.Sprintf("radius %0.2f:%s", e.radius, e.err)
}

func (e *areaError) IsRadiusNagative() bool {
    return e.radius < 0

}
func circleArea(radius float64) (float64, error) {
    if radius < 0 {
        return 0, &areaError{"Radius is negative", radius}
    }
    return math.Pi * radius * radius, nil
}
func main() {
    s, err := circleArea(-20)
    if err != nil {
        //将错误转换为具体的类型
        if err, ok := err.(*areaError); ok {
            fmt.Printf("Radius %.2f is less than zero", err.radius)
            return
        }
        fmt.Println(err)
        return
    }
    fmt.Println(s)
}

二、panic (抛出错误)和recover(捕获错误)

golang中没有try ... catch...这类异常捕获语句,但是提供了panic和recover内建函数,用于抛出异常以及异常的捕获。

• panic、 recover 参数类型为 interface{},因此可抛出任何类型对象。

• 如果程序出现了致命的错误,导致整个程序无法进行下去,golang提供了panic函数,用来实现程序的退出。

• 当程序发生 panic 时,使用 recover 可以重新获得对该程序的控

制。

• 不是所有的panic异常都来自运行时,直接调用内置的panic函数也会引发panic异常

• panic函数接受任何值作为参数。

(1)panic的使用

①延迟调⽤中引发的错误,可被后续延迟调⽤捕获,但仅最后⼀个错误可被捕获。

func test() {
defer func() {
    fmt.Println(recover())
}()
defer func() {
    panic("defer panic")
}()
    panic("test panic")
}
func main() {
    test()    //defer panic
}

②当函数发生 panic 时,它会终止运行,在执行完所有的延迟函数后,程序控制返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic 信息,接着打印出堆栈跟踪,最后程序终止。

** 如果函数没有 panic,调用 recover 函数不会获取到任何信息,也不会影响当前进程。**

demo

package main
import (
    "fmt"
)
func fullName(firstName *string, lastName *string) {
    if firstName == nil {
        panic("Firsr Name can't be null")
    }
    if lastName == nil {
        panic("Last Name can't be null")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}
func test(){
    defer fmt.Println("deferred call in test")
    firName := "paul"
    fullName(&firName, nil)
}
func main() {
    defer fmt.Println("deferred call in main")
    test()
    fmt.Println("returned normally from main")
}

输出

7BZvAfn.png!web

(2)recover的使用

如果 goroutine 没有 panic,那调用 recover 函数会返回 nil。
捕获函数 recover 只有在延迟调⽤内直接调⽤才会终⽌错误,否则总是返回 nil。任何未捕获的错误都会沿调⽤堆栈向外传递。

修改一下上面的例子使用recover来捕获异常

package main
import (
    "fmt"
)
func recoverName()  {
    if r := recover(); r != nil{
        fmt.Println("recovered from ",r)
    }
}
func fullName(firstName *string, lastName *string) {
    defer recoverName()
    if firstName == nil {
        panic("Firsr Name can't be null")
    }
    if lastName == nil {
        panic("Last Name can't be null")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}
func test(){
    defer fmt.Println("deferred call in test")
    firName := "paul"
    fullName(&firName, nil)
}
func main() {
    defer fmt.Println("deferred call in main")
    test()
    fmt.Println("returned normally from main")
}

输出为:

N3qU7bn.png!web

当发生panic之后,当前函数使用了recover,则捕获了这个错误,交给上一层调用者,正常执行剩下的代码;如果当前函数没有使用recover,调用者使用了recover,则属于调用者捕获了错误,将权限交给调用者的调用者,之后正常执行

recover函数捕捉了错误,但是这时我们并不容易发现错误的位置,那么可以在实现了recover函数的函数中使用debug.PrintStack(),这样就可以输出错误出现的函数,使用这个最先显示的行数是系统的,也就是stack.go包下的具体位置,这个会有两行,然后是调用debug.PrintStack()的地方,这个是自己写的函数,再然后就是系统的panic.go包,因为出错的时候的会调用这个包里面的函数,然后就是具体的错误位置了

函数原型:

func Stack() []byte

Stack 返回格式化的go程的调用栈踪迹。 对于每一个调用栈,它包括原文件的行信息和PC值;对go函数还会尝试获取调用该函数的函数或方法,及调用所在行的文本。

func PrintStack()

PrintStack将Stack返回信息打印到标准错误输出。

demo

import (
"fmt"
"runtime/debug"
)
func r() {
    if r := recover(); r != nil {
    fmt.Println("Recovered", r)
    debug.PrintStack()
    }
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK