乐趣区

关于go:Go异常处理

Go 异样解决

谬误与异样解决

  • 要辨别 Go 中的错误处理与异样解决

    • 错误处理指的是函数通过返回谬误的模式(个别是 error 类型的返回值)向调用方传递错误信息的形式,这种错误信息代表了函数运行的失败,但并不被认为是程序的异样,而是函数预期可能呈现的后果,能够进一步的解决,返回给用户或者执行重试等等,以晋升程序 鲁棒性
    • 异样机制被用来 解决未曾构想的谬误,也就是 bug,而不是强壮程序中失常呈现的谬误。Go 中异样与谬误的比照相似于 Java 中的非运行时异样与运行时异样的区别

      • Java 中的非运行时异样强制捕捉,因为其针对的是一些预期呈现的异样,强制性的捕捉能够晋升程序的鲁棒性,然而运行时异样针对的则是一些显著的逻辑谬误也就是 bug,比方数组越界等等,这些异样不强制捕捉,为的就是尽可能的裸露给程序员,以供修复

panic/recover

  • Go 语言应用 panic/recover 模式来处理错误

panic

应用场景
  • panic能够在任何中央触发,触发后程序中断,立刻执行 以后 goroutine 中定义的 defer 语句,随后,程序 解体并输入日志信息 。日志信息包含panic value(错误信息) 和函数调用的 堆栈跟踪信息(对于每个 goroutine,日志信息中都会有与之绝对的,产生 panic 时的函数调用堆栈跟踪信息),比方下边的例子

    panic: runtime error: integer divide by zero
    
    goroutine 1 [running]:
    github.com/JJLAAA/base/func/function.PanicTest(...)
            /Users/lijia/GoProjects/GoProject/HelloWorld/github.com/JJLAAA/base/func/function/panicTest.go:4
    main.main()
            /Users/lijia/GoProjects/GoProject/HelloWorld/github.com/JJLAAA/base/func/main.go:6 +0x12
    • 对于 defer 的执行机会须要留神的是,定义在可能触发 panic 异样的语句之后的 defer 语句并不会在异常中断后被执行,只有定义在之前的 defer 语句会执行
    • runtime 包提供了 Stack 办法用来输入 堆栈信息(不蕴含panic value,以更不便的诊断问题

      func main() {defer printStack()
          f(3)
      }
      func f(x int) {fmt.Printf("f(%d)\n", x+0/x) // panics if x == 0
          defer fmt.Printf("defer %d\n", x)
          f(x - 1)
      }
      func printStack() {var buf [4096]byte
          n := runtime.Stack(buf[:], false)
          os.Stdout.Write(buf[:n])
      }
      • 程序解体后,首先应用 printStack 打印堆栈信息,而后再打印 panic value + 堆栈信息, 在 Go 的 panic 机制中,提早函数的调用在开释堆栈信息之前,所以 Stack 办法能在函数堆栈开释前获取其信息
    • Go 同时提供了 recover 内置函数使得程序从 panic 异样中复原,以阻止程序解体
  • panic 异样有两种触发形式

    • 运行时的异样触发,比方做除法运算时,除数是 0,则会在运行时触发 panic 异样

      func PanicTest(div int) {print(1 / div)
      }
      
      func main() {function.PanicTest(0)
      }
      panic: runtime error: integer divide by zero
    • 应用 内置的 panic 函数 手动触发异样,个别在逻辑上不应该产生的场景中调用此 panic,以起到debug 的作用
应用准则
  • 一个强壮的程序内应该尽可能的应用 Go 提供的 谬误机制 解决预期的谬误,而尽量减少 panic 的应用,panic 个别用于严重错误,如程序外部的逻辑不统一等等
  • Go 语言的源码包中提供了一种 Must 为前缀的函数,这些函数的设计认为适度的应用 谬误机制 也是不适合的,比方《Go 语言圣经》中给到了 regexp.MustCompile 的例子,对该类型的函数的设计了解是:当调用者明确的晓得正确的输出不会引起函数谬误时,要求调用者查看这个谬误是不必要和累赘的。咱们应该假如函数的输出始终非法,当调用者输出了不应该呈现的输出时,触发 panic 异样。函数名中的 Must 前缀是一种针对此类函数的命名约定,示意要求调用者必须提供正确的参数,否则触发panic

    func MustCompile(str string) *Regexp {regexp, err := Compile(str)
        if err != nil {panic(`regexp: Compile(` + quote(str) + `): ` + err.Error())
        }
        return regexp
    }
    • 再举一个 html/template 包下的 Must 办法的例子

      var report = template.Must(template.New("issuelist").
          Funcs(template.FuncMap{"daysAgo": daysAgo}).
          Parse(templ))
      
      func Must(t *Template, err error) *Template {
          if err != nil {panic(err)
          }
          return t
      }
      • Must函数实际上是一种 断言机制 ,如果Parse 函数返回了谬误则代表参数 t 有效,此时触发 panic 异样
  • 个别库在 提供 Must 类型的函数的同时也会提供非 Must 类型的对应函数,这类函数则应用谬误机制解决,例如regexp.Compile

    func Compile(expr string) (*Regexp, error) {return compile(expr, syntax.Perl, false)
    }

recover

应用场景
  • 只管 panic 异样的目标在于揭发程序中的 bug,仿佛对于该异样,不必做任何解决,只须要等着他触发并查看异样信息即可,然而实际上在理论生产测试中,如果万一呈现了 panic 异样,还是须要在此时做一些必要的解救伎俩 ,比方:Web 程序中遇到重大的panic 异样时,应该在解体前敞开所有连贯,防止客户端期待,在开发测试阶段,甚至还能够将异样信息返回到客户端。因而,Go 同时提供了 recover 内置函数使得程序从 panic 异样中复原
  • recover该当在引发 panic 的语句之前定义的 defer 语句 中被调用 ,此时如果出现异常,以后函数将不再执行,然而 能够失常返回,而不至于引发程序解体 panic value 与函数堆栈信息也不会被打印
  • 如果没有出现异常的状况下调用 recover 函数或者是 panic 函数传入 nil 或者是未在 defer 语句中调用 recover 的状况下,recover函数的返回值是 nil,否则recover 函数返回 panic 函数传入的参数信息或者是运行时给的panic value
  • goroutine 中有多个 defer(recover)语句时,panic 异样最近的 defer(recover)语句将解决异样

    func RecoverTest(div int) {defer func() {fmt.Println(recover())
        }()
        fmt.Println(1 / div)
    }
    import (
        "fmt"
        "github.com/JJLAAA/base/func/function"
    )
    func main() {defer func() {fmt.Println(recover())
        }()
    
        function.RecoverTest(0)
        println("main end")
    }
    • RecoverTest办法中的 recover 语句会率先执行,而 main 办法中的 defer 语句则不会取得无效的panic value,只会取得nil
应用准则
  • recover函数能够实现 panic 异样到 error 的转化,从而实现程序的复原,见上面的例子,然而思考到二者的触发场景不同,不加区分的复原所有的 panic 异样,不是可取的做法,因为在 panic 之后,无奈保障包级变量的状态依然和咱们预期统一。比方,对数据结构的一次重要更新没有被残缺实现、文件或者网络连接没有被敞开、取得的锁没有被开释。此外,如果写日志时产生的 panic 被不加区分的复原,可能会导致破绽被疏忽。

    func Parse(input string) (s *Syntax, err error) {defer func() {if p := recover(); p != nil {err = fmt.Errorf("internal error: %v", p)
            }
        }()
        // ...parser...
    }
  • 一般来说,不应该试图去复原其余包引起的 panic。私有的 API 应该将函数的运行失败作为 error 返回,而不是panic。同样的,也不应该复原一个由别人开发的函数引起的panic,比如说调用者传入的回调函数,因为无奈确保这样做是平安的

    • 在一些特定场景下,比方 web 服务器中,不可能因为一个处理函数触发了 panic 异样,就间接关停服务器,web 服务器遇到处理函数导致的 panic 时会调用 recover,输入堆栈信息,持续运行。这样的做法在实践中很便捷,但也会引起资源透露,或是因为recover 操作,导致其余问题
  • 综上所述的应用 recover 解决 panic 异样的种种问题,平安的做法是 有选择性的 recover,只复原应该被复原的panic 异样 。为了标识某个panic 是否应该被复原,能够将 panic value 设置成非凡类型 。在recover 时对 panic value 进行查看,如果发现 panic value 是非凡类型,就将这个 panic 作为 error 解决,如果不是,则依照失常的 panic 进行解决

    // soleTitle returns the text of the first non-empty title element
    // in doc, and an error if there was not exactly one.
    func soleTitle(doc *html.Node) (title string, err error) {type bailout struct{}
        defer func() {switch p := recover(); p {
            case nil:       // no panic
            case bailout{}: // "expected" panic
                err = fmt.Errorf("multiple title elements")
            default:
                panic(p) // unexpected panic; carry on panicking
            }
        }()
        // Bail out of recursion if we find more than one nonempty title.
        forEachNode(doc, func(n *html.Node) {
            if n.Type == html.ElementNode && n.Data == "title" &&
                n.FirstChild != nil {
                if title != "" {panic(bailout{}) // multiple titleelements
                }
                title = n.FirstChild.Data
            }
        }, nil)
        if title == "" {return "", fmt.Errorf("no title element")
        }
        return title, nil
    }
    • 上述代码是页面爬虫提取 Title 的办法,当发现有多个 Title 时会触发 panic,并传入bailout 类型的 panic value,此时则能够将其转化为 error 返回,而其余未知状况则仍触发panic 异样

      • 实际上这种已知状况的异样更适宜间接用 error 示意,这里仅做演示
  • 当然,在非凡状况下,recover也无奈复原 panic 异样后解体的程序,比方 Go 运行时内存不足,则会间接终止程序

参考

  • Go 语言圣经
退出移动版