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

一、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")
}

输出


(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")
}

输出为:



当发生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()
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容