error接口
Go语言引入了一个关于错误处理的标准模式, 即error接口, 它是Go语言内建的接口类型, 该接口的定义如下:
type error interface{
Error() string
}
Go语言的标准库代码包errors(安装目录/src/errors/errors.go)为用户提供如下方法:
package errors
func New(text string) error {
return &errorString{text}
}
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
另一个可以生成error类型值的方法是调用fmt包(安装目录/src/fmt/print.go)中的Errorf函数:
package fmt
import "errors"
func Errorf(format string, a ...interface{}) error {
return errors.New(Sprintf(format, a...))
}
【实例】
package main
import(
"fmt"
"errors"
)
func main(){
var err1 error = errors.New("a normal err1")
fmt.Println(err1) //a normal err1
var err2 error = fmt.Errorf("%s", "a normal err2")
fmt.Println(err2) //a normal err2
}
函数通常在最后的返回值中返回错误信息:
package main
import(
"fmt"
"errors"
)
func Divide(a, b float64) (result float64, err error) {
if b == 0{
result = 0.0
err = errors.New("runtime error: divide by zero")
return
}
result = a / b
err = nil
return
}
func main(){
r, err := Divide(10.0, 0)
if err != nil {
fmt.Println(err) //错误处理:runtimne error: divide by zero
}else{
fmt.Println(r)
}
}
实例:
err.Error() 作用
package main
import (
"errors"
"fmt"
"reflect"
)
func Divide(a, b float64) (result float64, err error) {
if b == 0 {
result = 0.0
err = errors.New("runtime error: divide by zero")
return
}
result = a / b
err = nil
return
}
func main() {
r, err := Divide(10.0, 0)
if err != nil {
fmt.Println(err) //错误处理:runtimne error: divide by zero
fmt.Println(reflect.TypeOf(err)) //*errors.errorString
fmt.Println(reflect.TypeOf(err.Error())) //string Error()作用是转换为 sring 类型
} else {
fmt.Println(r)
}
}
panic
在通常情况下, 向程序使用方报告错误状态的方式可以是返回一个额外的error类型值。
但是, 当遇到不可恢复的错误状态的时候, 如数组访问越界、空指针引用等, 这些运行时错误会引起painc异常。这时, 上述错误处理方式显然就不适合了。
反过来讲, 在一般情况下, 我们不应通过调用panic函数来报告普通的错误, 而应该只把它作为报告致命错误的一种方式。当某些不应该发生的场景发生时, 我们就应该调用panic。
一般而言, 当panic异常发生时, 程序会中断运行, 并立即执行在该goroutine(可以先理解成线程, 在中被延迟的函数(defer 机制)。
随后, 程序崩溃并输出日志信息。日志信息包括panic value和函数调用的堆栈跟踪信息。
不是所有的panic异常都来自运行时, 直接调用内置的panic函数也会引发panic异常;panic函数接受任何值作为参数。
func panic(v interface{})
调用panic函数引发的panic异常:
package main
import (
"fmt"
)
func TestA() {
fmt.Println("func TestA()")
}
func TestB() {
panic("func TestB(): panic")
}
func TestC() {
fmt.Println("func TestC()")
}
func main() {
TestA()
TestB() //TestB()发生异常,中断程序
TestC()
}
运行结果:
func TestA()
panic: func TestB(): panic
goroutine 1 [running]:
main.TestB(...)
D:/Go/study/src/study.go:12
main.main()
D:/Go/study/src/study.go:21 +0x45
exit status 2
内置的panic函数引发的panic异常:
package main
import(
"fmt"
)
func TestA() {
fmt.Println("func TestA()")
}
func TestB(x int) {
var a [10]int
a[x] = 222 //x值为11时,数组越界
}
func TestC() {
fmt.Println("func TestC()")
}
func main() {
TestA()
TestB(11) //TestB()发生异常,中断程序
TestC()
}
运行结果:
func TestA()
panic: runtime error: index out of range
goroutine 1 [running]:
main.TestB(...)
D:/Go/study/src/study.go:13
main.main()
D:/Go/study/src/study.go:22 +0x2c
exit status 2
recover
运行时panic异常一旦被引发就会导致程序崩溃。这当然不是我们愿意看到的, 因为谁也不能保证程序不会发生任何运行时错误。
不过, Go语言为我们提供了专用于"拦截"运行时panic的内建函数——recover。它可以是当前的程序从运行时panic的状态中恢复并重新获得流程控制权。
func recover() interface{}
注意:recover只有在defer调用的函数中有效。
defer func(){
//recover() //拦截错误
fmt.Println(recover()); //打印错误的信息
}() //调用匿名函数,不要忘记()
如果调用了内置函数recover, 并且定义该defer语句的函数发生了panic异常, recover会使程序从panic中恢复, 并返回panic value。
导致panic异常的函数不会继续运行, 但能正常返回。在未发生panic时调用recover, recover会返回nil。
【实例】
package main
import(
"fmt"
)
func TestA() {
fmt.Println("func TestA()")
}
func TestB() (err error) {
defer func() { //在发生异常时,设置恢复
if x := recover(); x != nil {
//panic value被附加到错误信息中;
//并用err变量接收错误信息,返回给调用者。
err = fmt.Errorf("internal error: %v", x)
}
}()
panic("func TestB(): panic")
}
func TestC() {
fmt.Println("func TestC()")
}
func main() {
TestA()
err := TestB()
fmt.Println(err)
TestC()
/*
运行结果:
func TestA()
internal error: func TestB(): panic
func TestC()
*/
}
延迟调用中引发的错误, 可被后续延迟调用捕获, 但仅最后一个错误可被捕获:
package main
import(
"fmt"
)
func test() {
defer func() {
fmt.Println(recover())
}()
defer func() {
panic("defer panic") //最后一个执行的被捕获
}()
panic("test panic")
}
func main() {
test()
//运行结果:defer panic
}
注意: defer 存在着后进先出的特性, 因此第一个错误是: panic("test panic"), 第二个错误是: panic("defer panic")
【实例】
package main
import(
"fmt"
)
func TestA() {
fmt.Println("func TestA()")
}
func TestB(x int) {
defer func(){
//recover() //拦截错误
fmt.Println(recover()); //打印错误的信息
}() //调用匿名函数,不要忘记()
var a [10]int
a[x] = 222 //x值为11时,数组越界
}
func TestC() {
fmt.Println("func TestC()")
}
func main() {
TestA()
TestB(11) //TestB()发生异常,中断程序
TestC()
}
自定义error
package main
import (
"fmt"
"os"
"time"
)
type PathError struct {
path string
op string
createTime string
message string
}
func (p *PathError) Error() string {
return fmt.Sprintf("path=%s \nop=%s \ncreateTime=%s \nmessage=%s", p.path,
p.op, p.createTime, p.message)
}
func Open(filename string) error {
file, err := os.Open(filename)
if err != nil {
return &PathError{
path: filename,
op: "read",
message: err.Error(),
createTime: fmt.Sprintf("%v", time.Now()),
}
}
defer file.Close()
return nil
}
func main() {
err := Open("/Users/5lmh/Desktop/go/src/test.txt")
switch v := err.(type) {
case *PathError:
fmt.Println("get path error,", v)
default:
}
}
输出结果:
get path error, path=/Users/pprof/Desktop/go/src/test.txt
op=read
createTime=2018-04-05 11:25:17.331915 +0800 CST m=+0.000441790
message=open /Users/pprof/Desktop/go/src/test.txt: no such file or directory
golang recover捕获不到当前函数中的panic异常
package main
import(
"fmt"
"bytes"
"log"
)
func main(){
var buff bytes.Buffer
// 自定义一个日志对象
// 默认的日志写入到buff中
myLog := log.New(&buff, "", log.LstdFlags)
// 写入日志
myLog.Println("line 1")
myLog.Printf("line %d", 2)
myLog.Panic("log panic")
defer func(){
if err := recover(); err != nil{
fmt.Println("err: ",err)
}
}()
}
原因是defer执行的函数在panic之后才被调用, 也就是说defer根本就没有运行, 因为myLog.Panic已经终止了函数调用, 正确的做法应该是将defer执行的函数移到前面
defer执行的函数需要在panic之前调用
package main
import(
"fmt"
"bytes"
"log"
)
func main(){
defer func(){
if err := recover(); err != nil{
fmt.Println("err: ",err)
}
}()
var buff bytes.Buffer
// 自定义一个日志对象
// 默认的日志写入到buff中
myLog := log.New(&buff, "", log.LstdFlags)
// 写入日志
myLog.Println("line 1")
myLog.Printf("line %d", 2)
myLog.Panic("log panic")
}
结论: 当然还有一种情况也会捕获不到, 那就是发生异常的代码在另外一个协程, 调用它的函数无法捕获到其中的异常, 这种情况需要在协程中进行捕获;