一、error
错误表示程序中出现了异常情况。Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
• error类型是go语言的一种内置类型,使用的时候不用特定去import因为它本质上是一个接口
error类型是一个接口类型,这是它的定义:
type error interface { Error() string }
(1)一个例子理解error
package mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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() } }
作者:学生黄哲
链接:https://www.jianshu.com/p/18dfd4772cdb