Golang漂亮的错误处理规范也是Go语言的最大亮点之一。

error接口

标准库把error定义为接口类型, 以便于自己定义错误类型

1
2
3
type error interface{<!-- -->
    Error() string
}

error的使用也比较简单

1
2
3
4
5
6
7
8
    // error对象的两种创建方式
    // 1.使用fmt.Errorf
    //var err1 error = fmt.Errorf("%s", "this is normal error")
    err1 := fmt.Errorf("%s", "this is normal error")
    fmt.Println("err1 = ", err1)
    // 2. 使用errors.New()创建
    err2 := errors.New("this is normal error2")
    fmt.Println("err2 = ", err2)

再看看error的基本使用。
对于大多数函数,如果要返回错误,大致都可以定义为如下模式,将error作为返回值的最后一个,但这并非是强制要求的:

1
2
3
func Foo (param int) (n int, err error) {<!-- -->
    //...
}

调用时的代码建议按照如下的方式进行处理错误

1
2
3
4
5
6
7
n, err := Foo(0)

if err != nil {<!-- -->
    // 错误处理
} else {<!-- -->
    // 使用返回值n进行逻辑处理
}

举个栗子说明一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func TestUseError() {<!-- -->
    result, err := MyDiv(10, 0)
    if err != nil {<!-- -->
        // err不为空,那就说明出错了,那就打印错误信息
        fmt.Println("err = ", err)
    } else {<!-- -->
        // err为空,那就说明没有错误,那就打印结果
        fmt.Println("result = ", result)
    }
}

func MyDiv(a, b int) (res int, err error) {<!-- -->
    if b == 0 {<!-- -->
        err = errors.New("分母不能为0")
    } else {<!-- -->
        res = a / b
    }
    return
}

自定义error类型

因为Go语言接口的灵活性,根本不需要从error接口继承或者像Java一样使用implement来声明继承自哪个接口,具体代码如下:

1
2
3
4
5
6
7
8
type pathError struct {<!-- -->
    Op string
    Path string
    Err error
}
func (e *Patherror) Error() string {<!-- -->
    return e.Op + " " + e.Path + ": " + e.Err.Error()
}

panic()和recover()

Go语言中有两个内置函数panic()和recover()用来报告和处理运行时错误和程序中的错误。

  • error用于返回错误信息
  • panic()函数用于处理运行时异常,例如下标越界,空指针等,当panic异常发生时,程序会中断运行
  • recover()函数用于终止错误处理流程,也就是可以用来捕获并返回panic提交的错误内容,并使程序不要中断运行,但是recover()函数必须在defer中设置(连续调用panic,仅仅最后一个会被recover捕获)

一般recover的代码模板为:

1
2
3
4
5
6
7
8
9
defer func() {<!-- -->
    if err:= recover(); err != nil {<!-- -->
        fmt.Println(err)
    }
}()
foo()
// 无论foo()中是否会触发错误处理流程,该匿名defer函数都会在函数退出时得到执行。
// 假如foo()触发了错误处理流程,recover()函数执行将会使得该错误流程终止。
// 如果错误处理流程被触发,程序传给panic函数的参数不为nil,那么err中存的错误信息将会被打印出来。

下面在实践中体会一下panic和recover的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func TestUsePanicAndRecover() {<!-- -->
    test1()
    test2(20)
    test3()
}
func test1() {<!-- -->
    fmt.Println("1111111111111111111")
}

func test2(x int) {<!-- -->
    //如果不想让程序崩溃,那就需要设置一个recover()函数
    defer func() {<!-- -->
        //recover() // 直接调用recover()函数,结果就是这段代码直接跳过
        // 选择打印recover(),这样就可以把panic中的错误信息打印出来
        if err := recover(); err != nil {<!-- -->
            // 这里加一个判断,如果这里出错了,那么err就不为空,那就打印recover中的内容,否则
            fmt.Println(err)
        }
    }() // () 在这里是调用匿名函数
    var a [10]int
    a[x] = 100000 //这里会报一个数组下标越界的异常,产生一个panic,导致程序崩溃
}

func test3() {<!-- -->
    fmt.Println("3333333333333333333333")
}