文章

异常处理

什么是错误error

错误指的是可能出现问题的地方出现了问题,比如打开一个文件时失败,这种情况在人们的意料之中

和错误类似的还有一个异常,指的是不应该出现问题的地方出现了问题。比如引用了空指针,这种情况在人们的意料之外,可见,错误是业务过程的一部分,而异常不是

package main

import (
   "fmt"
   "os"
)

func main() {

   //打开一个文件
   file, err := os.Open("aaa.txt")
   if err != nil {
      fmt.Println(err)
      return
   }
   fmt.Println(file.Name())
}

image-20221223125345031

在实际工程项目中,我们希望通过程序的错误信息快速定位问题,但是又不喜欢错误处理代码写的冗余而又哆嗦。

Go语言没有提供像java等语言中的try…catch异常处理方式,而是通过函数返回值逐层往上抛

这种设计,鼓励工程师在代码中显式的检查错误,而非忽略错误

好处就是避免漏掉本应处理的错误。但是带来了一个弊端,让代码繁琐

Go中的错误也是一种类型,错误用内置的error类型表示,就像其他类型的,如int,float64.

错误值可以存储在变量中,从函数中返回等等。

创建自己的error错误信息

package main

import (
   "errors"
   "fmt"
)

func main() {
   errinfo := errors.New("我是一个错误信息")
   fmt.Println(errinfo)
   fmt.Printf("%T\n", errinfo)

   err := setAge(-1)
   if err != nil {
      fmt.Println(err)
   }

   errinfo2 := fmt.Errorf("我是一个错误%d", 500)
   fmt.Println(errinfo2)
   fmt.Printf("%T\n", errinfo2)

}
func setAge(age int) error {
   if age < 0 {
      return errors.New("年龄输入不合法")
   }
   fmt.Println(age)
   return nil
}

image-20221223150417120

错误类型

error类型是一个接口类型

package main

import (
   "fmt"
   "net"
)

type error interface {
   Error() string
}

func main() {
   addrs, err := net.LookupHost("www.baidu.com")
   fmt.Println(err)
   dnsError, ok := err.(*net.DNSError)
   if ok {
      if dnsError.Timeout() {
         fmt.Println("超时")
      } else if dnsError.Temporary() {
         fmt.Println("临时错误")
      } else {
         fmt.Println("其他错误")
      }
   }
   fmt.Println(addrs)
}

image-20221223151328241

通过断言,来判断具体的错误类型,然后进行对应的错误处理

自定义error

自定义错误,需要实现error()接口中的Error()方法

package main

import "fmt"

type MyDiyError struct {
   msg  string
   code int
}

func (e MyDiyError) Error() string {
   return fmt.Sprint("错误信息:", e.msg, "状态码:", e.code)
}
func test1(i int) (int, error) {
   if i != 0 {
      return i, &MyDiyError{"非零数据", 500}
   }
   return i, nil
}
func main() {
   i, err := test1(-1)
   if err != nil {
      fmt.Println(err)
      myerr, ok := err.(*MyDiyError)
      if ok {
         fmt.Println(myerr.msg)
         fmt.Println(myerr.code)
      }
   }
   fmt.Println(i)
}

image-20221223152949603

panic和recover

panic

如果函数F中书写并出发了panic语句,会终止其后要执行的代码。在panic所在函数F内如果存在要执行的defer函数列表,则按照defer书写顺序的逆序执行

package main

import "fmt"

func main() {
   defer fmt.Println("main-----1")
   defer fmt.Println("main-----2")
   fmt.Println("main-----3")
   test(1)
   fmt.Println("main----4")
   defer fmt.Println("main----5")
}
func test(num int) {
   defer fmt.Println("test-----1")
   defer fmt.Println("test-----2")
   fmt.Println("test-----3")
   if num == 1 {
      panic("出现一次----panic")

   }
   fmt.Println("test----4")
   defer fmt.Println("test----5")
}

image-20221223155221233

  • 触发panic
  • 逆序执行test函数中的panic前的2条defer语句
  • 返回外部函数(main函数)
  • 逆序执行main函数中的test函数前的2条defer语句
  • 抛出异常

由于panic抛出异常,可以看到test函数panic语句后面的语句都不被执行;返回外部函数后,test函数后的语句也都不被执行

recover

  • recover的作用是捕获panic,从而恢复正常代码执行
  • recover必须配合defer使用
  • recover没有传入参数,但是有返回值,返回值就是panic传递的值
package main

import "fmt"

func main() {
   defer fmt.Println("main-----1")
   defer fmt.Println("main-----2")
   fmt.Println("main-----3")
   test(1)
   fmt.Println("main----4")
   defer fmt.Println("main----5")
}
func test(num int) {
   defer func() {
      msg := recover()
      if msg != nil {
         fmt.Println("msg:", msg, "-----程序恢复了执行")
      }
   }()

   defer fmt.Println("test-----1")
   defer fmt.Println("test-----2")
   fmt.Println("test-----3")
   if num == 1 {
      panic("出现一次----panic")

   }
   fmt.Println("test----4")
   defer fmt.Println("test----5")
}

image-20221223160740150

  • 触发panic
  • 逆序执行test函数中的panic前的3条defer语句
  • 执行到第三条defer时,recover恢复恐慌并输出信息
  • 返回外部函数,程序继续执行

由于recover恢复恐慌,所以程序就不会因为panic而退出执行,外部函数还能正常执行下去

License:  CC BY 4.0 test