异常简介

一、异常分类

1.1、Panic

1、GO中Panic是一种系统级别的异常,一旦出现回终端程序的运行,来自运行时。并且当我们手动调用panic()函数的时候也会触发这个异常。用于严重错误,此时不管出现在哪里Panic都会直接结束程序(除非进行恢复)

2、手动触发panic()如下:

func main(){
//do something
panic("panic happend !! ")
//do sonmething will not be excute
}

panic()触发的时候,会立即在当前GO程中执行一个 defer[^1] ,并且其实际会输出详细的堆栈调用日志。

1.2、Error

1.2.1、errors.go

1、Error 在GO中默认使用返回值的方法进行返回,要不你接受这个错误进行处理,要不使用_匿名接收这个错误不处理。

  • nil:表示没有错误

2、手动抛出一个error

func main(){
throwError()
}

func throwError() (err error ){
//do something
return errors.New("this is a new error")
}

3、errors
1、errors源码如下:

package errors  

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
s string
}

// output error detail
func (e *errorString) Error() string {
return e.s
}

根据errors.go我们可以按照他的方法自定义一个错误类型并且实现错误error接口,实现自定义错误类型。

// error 接口
type error interface {
Error() string
}

官方实例代码:

package errors_test  

import (
"fmt"
"time")

// MyError is an error implementation that includes a time and message.
type MyError struct {
When time.Time
What string
}

func (e MyError) Error() string {
return fmt.Sprintf("%v: %v", e.When, e.What)
}

func oops() error {
return MyError{
time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
"the file system has gone away",
}}

func Example() {
if err := oops(); err != nil {
fmt.Println(err)
} // Output: 1989-03-15 22:30:00 +0000 UTC: the file system has gone away
}

1.2.2、warp.go

参考:

  1. 深入Go:错误的包装与解包 - 腾讯云开发者社区-腾讯云 (tencent.com)
  2. Go 编程: 错误的诞生与死亡:包装、检查与处理 | 码农网 (codercto.com)

实际上包裹了一个error错误链,具体原理如下:

1.2.2.1、Unwrap(err error) error

Unwrap(err error) error :获取内层的error,返回错误根本原因

  1. 包装Error:fmt.Errorf 来包装 error,需要使用 %w 的格式化
    return fmt.Errorf("add error info: %+v, origin error: %w", "other info", err)
  2. 解包Error
    源码:
    func Unwrap(err error) error {  
    //断言转化 err类型为一个实现了Unwarp()方法的interface对象
    u, ok := err.(interface {
    Unwrap() error
    })
    //断言失败
    if !ok {
    return nil
    }
    //继续解包
    return u.Unwrap()
    }

1.2.2.2、Is(err, target error) bool

Is(err, target error) bool :查看调用链是否与包含target错误
源码:

func Is(err, target error) bool {  
if target == nil {
return err == target
}
//类型是否可比
isComparable := reflectlite.TypeOf(target).Comparable()
for {
//与目标 err相同
if isComparable && err == target {
return true
}
//递归继续向下查找错误链上是否包含指定目标错误
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
//解包到最后依然不存在,没有找到
if err = Unwrap(err); err == nil {
return false
}
}}

1.2.2.3、As(err error, target interface{}) bool

As(err error, target interface{}) bool : 剥离到给定target并将target设置到属性中去
源码:

func As(err error, target any) bool { 
//校验转化目标是否为空
//判空
if target == nil {
panic("errors: target cannot be nil")
}
//拿到value
val := reflectlite.ValueOf(target)
//拿到value的实际类型reflect.type
typ := val.Type()
//实际目标类型不是一个指针类型,或者是一个空指针类型
if typ.Kind() != reflectlite.Ptr || val.IsNil() {
panic("errors: target must be a non-nil pointer")
}
//取出指针地址指向的对象
targetType := typ.Elem()
//判断其类是不是一个接口类型,以及是否实现了这个错误类型
if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
panic("errors: *target must be interface or implement error")
}

//校验待转化错误是否为空
for err != nil {
//判断是否可以将err分配到目标对象
if reflectlite.TypeOf(err).AssignableTo(targetType) {
//使用值进行设置到目标对象
val.Elem().Set(reflectlite.ValueOf(err))
return true
}
//err实现了As(any) bool方法断言成功并且执行方法进行转化
if x, ok := err.(interface{ As(any) bool }); ok && x.As(target) {
return true
}
//继续剥掉一层error看内部是否与给定匹配
err = Unwrap(err)
}
return false
}

二、异常处理

2.1、Panic

在Panic错误中一半使用recover函数进行恢复整个已经崩溃的程序进程,但是必须在defer中进行调用,才会生效。并且在函数调用的过程中可以在每个函数中都抛出panic逐层向外,直到不能捕获就会结束。

  • 有 panic 没 recover ,程序宕机。
  • 有 panic 有 recover ,程序正常。

使用方法:

func main(){
panicTest()
}

func panicTest(){
//do thomething

//捕获Panic
defer func (){
if err:= recover(); err != nil{
//deal error
}
}
}

2.2、Error

在GO语言中不存在传统意义上面try -catch语句,其设计思想如下:

  • 函数出现异常,就会将异常作为返回值,不存在异常的时候就会返回nil
  • 在外部应该使用if进行捕获异常进行处理
err,ans = ErrorDemo()
if err != nil{
//处理异常
}

三、优雅异常处理

#todo

  • 优雅的异常处理

[^1]:  defer_语句会将其后面跟随的语句进行延迟处理,在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行(多个执行入栈,出栈)