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

640?wx_fmt=png


原文作者:学生黄哲
链接:https://www.jianshu.com/p/18dfd4772cdb
來源:简书

一、error

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


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


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


 
 
1type error interface {2    Error() string3}interface {
2    Error() string
3}

(1)一个例子理解error


 
 
 1package main 2import ( 3    "fmt" 4    "os" 5) 6func main() { 7    //试图打开一个并不存在的文件,这将会返回一个error 8    f, err := os.Open("/test.txt") 9    if err != nil {10        fmt.Println(err) //no such file or directory11        return12    }13    fmt.Println(f.Name(), "opened successfully")14}package main
2import (
3    "fmt"
4    "os"
5)
6func main() {
7    //试图打开一个并不存在的文件,这将会返回一个error
8    f, err := os.Open("/test.txt")
9    if err != nil {
10        fmt.Println(err) //no such file or directory
11        return
12    }
13    fmt.Println(f.Name(), "opened successfully")
14}


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


(2)错误定制


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


第一、通过errors包去订制error


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


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


demo


 
 
 1package main 2import ( 3    "errors" 4    "fmt" 5    "math" 6) 7func circleArea(radius float64) (float64, error) { 8    if radius < 0 { 9        //使用字符串创建一个错误10        return 0, errors.New("Area calculation failed, radius is less than zero")11    }12    return math.Pi * radius * radius, nil13}14func main() {15    radius := -20.016    area, err := circleArea(radius)17    if err != nil {18        fmt.Println(err)19        return20    }21    fmt.Printf("Area of circle %0.2f", area)22}package main
2import (
3    "errors"
4    "fmt"
5    "math"
6)
7func circleArea(radius float64) (float64, error) {
8    if radius < 0 {
9        //使用字符串创建一个错误
10        return 0, errors.New("Area calculation failed, radius is less than zero")
11    }
12    return math.Pi * radius * radius, nil
13}
14func main() {
15    radius := -20.0
16    area, err := circleArea(radius)
17    if err != nil {
18        fmt.Println(err)
19        return
20    }
21    fmt.Printf("Area of circle %0.2f", area)
22}

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


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


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


就不贴demo了
只需要把circleArea里if语句的返回值改为


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

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


 
 
 1type MyError struct { 2err error  3} 4//订制Error() 5func (e MyError) Error() string { 6    return e.err.Error() 7} 8func main() { 9   err:=MyError{10        errors.New("error"),11   }12   fmt.Println(err.Error())13}type MyError struct {
2err error 
3}
4//订制Error()
5func (e MyError) Error() string {
6    return e.err.Error()
7}
8func main() {
9   err:=MyError{
10        errors.New("error"),
11   }
12   fmt.Println(err.Error())
13}


demo


 
 
 1package main 2import ( 3    "fmt" 4    "math" 5 6) 7type areaError struct { 8    err    string 9    radius float6410}11func (e *areaError) Error() string {12    return fmt.Sprintf("radius %0.2f:%s", e.radius, e.err)13}1415func (e *areaError) IsRadiusNagative() bool {16    return e.radius < 01718}19func circleArea(radius float64) (float64, error) {20    if radius < 0 {21        return 0, &areaError{"Radius is negative", radius}22    }23    return math.Pi * radius * radius, nil24}25func main() {26    s, err := circleArea(-20)27    if err != nil {28        //将错误转换为具体的类型29        if err, ok := err.(*areaError); ok {30            fmt.Printf("Radius %.2f is less than zero", err.radius)31            return32        }33        fmt.Println(err)34        return35    }36    fmt.Println(s)37}package main
2import (
3    "fmt"
4    "math"
5
6)
7type areaError struct {
8    err    string
9    radius float64
10}
11func (e *areaError) Error() string {
12    return fmt.Sprintf("radius %0.2f:%s", e.radius, e.err)
13}
14
15func (e *areaError) IsRadiusNagative() bool {
16    return e.radius < 0
17
18}
19func circleArea(radius float64) (float64, error) {
20    if radius < 0 {
21        return 0, &areaError{"Radius is negative", radius}
22    }
23    return math.Pi * radius * radius, nil
24}
25func main() {
26    s, err := circleArea(-20)
27    if err != nil {
28        //将错误转换为具体的类型
29        if err, ok := err.(*areaError); ok {
30            fmt.Printf("Radius %.2f is less than zero", err.radius)
31            return
32        }
33        fmt.Println(err)
34        return
35    }
36    fmt.Println(s)
37}

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


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


• panic、 recover 参数类型为 interface{},因此可抛出任何类型对象。
• 如果程序出现了致命的错误,导致整个程序无法进行下去,golang提供了panic函数,用来实现程序的退出。
• 当程序发生 panic 时,使用 recover 可以重新获得对该程序的控
制。
• 不是所有的panic异常都来自运行时,直接调用内置的panic函数也会引发panic异常
• panic函数接受任何值作为参数。
(1)panic的使用


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


 
 
 1func test() { 2defer func() { 3    fmt.Println(recover()) 4}() 5defer func() { 6    panic("defer panic") 7}() 8    panic("test panic") 9}10func main() {11    test()    //defer panic12}func test() {
2defer func() {
3    fmt.Println(recover())
4}()
5defer func() {
6    panic("defer panic")
7}()
8    panic("test panic")
9}
10func main() {
11    test()    //defer panic
12}

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

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


 
 
 1package main 2import ( 3    "fmt" 4) 5func fullName(firstName *string, lastName *string) { 6    if firstName == nil { 7        panic("Firsr Name can't be null") 8    } 9    if lastName == nil {10        panic("Last Name can't be null")11    }12    fmt.Printf("%s %s\n", *firstName, *lastName)13    fmt.Println("returned normally from fullName")14}15func test(){16    defer fmt.Println("deferred call in test")17    firName := "paul"18    fullName(&firName, nil)19}20func main() {21    defer fmt.Println("deferred call in main")22    test()23    fmt.Println("returned normally from main")24}package main
2import (
3    "fmt"
4)
5func fullName(firstName *string, lastName *string) {
6    if firstName == nil {
7        panic("Firsr Name can't be null")
8    }
9    if lastName == nil {
10        panic("Last Name can't be null")
11    }
12    fmt.Printf("%s %s\n", *firstName, *lastName)
13    fmt.Println("returned normally from fullName")
14}
15func test(){
16    defer fmt.Println("deferred call in test")
17    firName := "paul"
18    fullName(&firName, nil)
19}
20func main() {
21    defer fmt.Println("deferred call in main")
22    test()
23    fmt.Println("returned normally from main")
24}


输出


640?wx_fmt=other


(2)recover的使用


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


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


 
 
 1package main 2import ( 3    "fmt" 4) 5func recoverName()  { 6    if r := recover(); r != nil{ 7        fmt.Println("recovered from ",r) 8    } 9}10func fullName(firstName *string, lastName *string) {11    defer recoverName()12    if firstName == nil {13        panic("Firsr Name can't be null")14    }15    if lastName == nil {16        panic("Last Name can't be null")17    }18    fmt.Printf("%s %s\n", *firstName, *lastName)19    fmt.Println("returned normally from fullName")20}21func test(){22    defer fmt.Println("deferred call in test")23    firName := "paul"24    fullName(&firName, nil)25}26func main() {27    defer fmt.Println("deferred call in main")28    test()29    fmt.Println("returned normally from main")30}package main
2import (
3    "fmt"
4)
5func recoverName()  {
6    if r := recover(); r != nil{
7        fmt.Println("recovered from ",r)
8    }
9}
10func fullName(firstName *string, lastName *string) {
11    defer recoverName()
12    if firstName == nil {
13        panic("Firsr Name can't be null")
14    }
15    if lastName == nil {
16        panic("Last Name can't be null")
17    }
18    fmt.Printf("%s %s\n", *firstName, *lastName)
19    fmt.Println("returned normally from fullName")
20}
21func test(){
22    defer fmt.Println("deferred call in test")
23    firName := "paul"
24    fullName(&firName, nil)
25}
26func main() {
27    defer fmt.Println("deferred call in main")
28    test()
29    fmt.Println("returned normally from main")
30}


输出为:


640?wx_fmt=other


当发生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


 
 
 1import ( 2"fmt" 3"runtime/debug" 4) 5func r() { 6    if r := recover(); r != nil { 7    fmt.Println("Recovered", r) 8    debug.PrintStack() 9    }10}import (
2"fmt"
3"runtime/debug"
4)
5func r() {
6    if r := recover(); r != nil {
7    fmt.Println("Recovered", r)
8    debug.PrintStack()
9    }
10}



作者:学生黄哲
链接:https://www.jianshu.com/p/18dfd4772cdb
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值