跳到主要内容

错误和异常处理

 程序在运行过程中,难免会出现错误。例如除数为0,想要打开的文件不存在等。go 中处理异常情况的方式有两种:errorpanic ,本文先介绍这两种方式的用法,最后比较两者的使用场景。

1. error

 在buildin包可以找到error的定义,它是接口类型,只有一个Error方法,返回一个string

type error interface {
Error() string
}

 很多系统函数的返回值包含error类型,例如os包里面的Open方法。

func Open(name string) (*File, error)

 下面代码尝试打开一个并不存在的文件,观察Open方法返回的错误信息。

package main

import (
"log"
"os"
)

func main() {

_, err := os.Open("abc.tex")

if err != nil {
log.Fatal(err)
}
}

errors 包提供了 error 的操作函数,下面结合源码介绍如何新建 errorerrors 包里定义了errerString这个结构体,它实现了error接口(接口实现参考接口类型)。New方法返回errorString

type errorString struct {
s string
}

func (e *errorString) Error() string {
return e.s
}

func New(text string) error {
return &errorString{text}
}

 下面例子中,divide函数判断除数是否为0,如果为0就会返回错误给调用者。

package main

import (
"errors"
"fmt"
)

func divide(a, b int) (int, error) {

if b == 0 {
return 0, errors.New("除数不能为0")
}

ret := a / b
return ret, nil
}
func main() {

ret, err := divide(5, 0)

if err != nil {
fmt.Println(err.Error())
} else {
println(ret)
}

}

 用户可以自定义不同类型的 error,通过类型断言来判断不同的 error 进而处理不同的逻辑。例如:

package main

import (
"fmt"
"math/rand"
)

type error1 struct{}

type error2 struct{}

type error3 struct{}

func (e *error1) Error() string {
return "error1"
}

func (e *error2) Error() string {
return "error2"
}

func (e *error3) Error() string {
return "error3"
}

func test() (int, error) {
switch c := rand.Intn(100); c % 3 {
case 0:
return c, &error1{}
case 1:
return c, &error2{}
case 2:
return c, &error3{}
}
return 0, nil
}

func main() {

for i := 0; i < 10; i++ {
_, err := test()
switch v := err.(type) {
case *error1:
fmt.Printf("\nerror1 %s", v)
case *error2:
fmt.Printf("\nerror2 %s", v)
case *error3:
fmt.Printf("\nerror3 %s", v)
}
}

}

2. panic

panic 类似其他编程语言里的 Exception ,它指程序进入某种异常状态,需要中断当前函数的执行,并将错误立刻返回给调用者,如果调用者无法处理,那么错误会层层上抛,直到程序崩溃退出。

go 提供了内建函数panic供代码调用,panic函数的定义如下,它可以接受任意类型的参数。

func panic(interface{})

 下面例子在函数里显示的抛出了异常,最后程序终止。

package main

import (
"fmt"
"time"
)

func testA() {
fmt.Println("start in test A")
testB()
}

func testB() {
fmt.Println("start in testB")
testC()
}

func testC() {
fmt.Println("start in test C")
panic("error in c")
}

func main() {

go testA()

time.Sleep(3 * time.Second)
fmt.Printf(" main exit")
}

go 没有 throw,catch 这样的异常处理机制,但提供了类似的异常处理方式:deferrecoverdefer 语句在go defer语句使用中有介绍,recoverpanic类似,也是内建函数。

func recover() interface{}

 修改上面异常退出的例子,用recover函数捕获panic,程序显示正常退出。

package main

import (
"fmt"
"time"
)

func testA() {

fmt.Println("start in test A")
testB()
}

func testB() {

defer func() {
if err := recover(); err != nil {
fmt.Printf("catch error in test B %s", err)
}
}() //匿名函数捕获异常

fmt.Println("start in test B")
testC()
}

func testC() {
fmt.Println("start in test C")
panic("error in c")
}

func main() {

go testA()

time.Sleep(3 * time.Second)
fmt.Printf(" main exit")
}

recover捕获异常有以下几点要求:

  1. 必须在 defer 的函数里调用recover方法。
package main

import (
"fmt"
"time"
)

func testA() {

defer recover()

if err := recover(); err != nil {
fmt.Printf("\nrecover not in defer %s", err)
}
panic("error occur")

fmt.Printf("unreachable") //永远不会执行
}

func main() {

go testA()

time.Sleep(3 * time.Second)
fmt.Printf(" main exit")
}

  1. panicrecover 必须是同一个 goroutine
package main

import (
"fmt"
"time"
)

func testA() {
panic("error occur")
}

func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()

go testA()

time.Sleep(3 * time.Second)
fmt.Printf(" main exit")
}

提示

 如何在recover后修改返回值?试试named return value


  1. Error handling and Go

  2. Goroutines, Deferred Function Calls and Panic/Recover

  3. Defer, Panic, and Recover

署名-非商业性使用-禁止演绎 4.0 国际