IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> go 异常处理 -> 正文阅读

[网络协议]go 异常处理

panic-recover机制

go中不像其它语言有try catch语法,同样也不推荐捕获异常而是推荐采用将异常返回的方式。但是对于一些会导致程序崩溃的严重异常,直接退出也会比层层返回方便的多。因此go中实现了panic-recover机制,专门用于对这些异常进行抛出和恢复。
在语义上,try catch更像尝试性的去做一些事,在这期间如果发生错误,则如何如何。而panic则是告诉你程序奔溃了,发生了特别严重的故障,到了难以恢复的程度。recover则是看看能不能救回来,如果不是极其致命,那么就继续服务吧,打印一下错误信息,返回一个500,应用继续运行。

基础语法

  1. recover 必须要放在defer中延迟执行
  2. recover 必须要是defer闭包或者调用函数下的第一层调用

下例中recover 1理论上是最后执行的。但是recover 2,3,4都无法捕获panic。所以结果是打印recover 1。

func testDefer1() {

	defer func() {
		// recover 必须要在defer闭包(函数)下的第一层调用
		err := recover()
		if err != nil {
			fmt.Println("recover 1", err)
		}
	}()

	defer func() {
		func() {
			err := recover()
			if err != nil {
				fmt.Println("recover 2", err)
			}
		}()
	}()

	defer recover()

	err := recover()
	if err != nil {
		fmt.Println("recover 3", err)
	}

	panic("1233")

	err = recover()
	if err != nil {
		fmt.Println("recover 4", err)
	}

}

  1. defer 中可以陆续再panic,但是recover接收的是最后一个:

下例中打印的是233:

// defer 中panic,recover接收的是最后一个
func testError2() {

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

	defer func() {
		panic("233")
	}()

	panic("err")
}

  1. recover一次后,后续recover将不再会被触发,结果是panic err, no err
// 使用函数来接收recover,这应该是更值得推荐的写法
func clearPanic() {
	if err := recover(); err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("no err")
	}
}

func testError3() {
	// recover一次后,后续recover将不再会被触发
	defer clearPanic()
	defer clearPanic()

	panic("panic err")
}

  1. 可以利用recover 来模拟try catch

func try(code func(), catch func(interface{})) {
	defer func() {
		if err := recover();err != nil {
			catch(err)
		}
	}()
	code()
}

// 利用defer 模拟try catch 结构
func testError4() {
	try(func() {
		panic("err")
	}, func(err interface{}) {
		fmt.Println("catch", err)
	})
}

  1. 使用panic-recover 无疑是会增加性能损耗的,以下对比测试了和直接返回error的性能差距:

func panicError() {
	panic("this is panic")
}

func returnError() error {
	return errors.New("this is error")
}

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

func checkError() {
	if err := returnError(); err != nil {
		fmt.Println(err)
	}
}

func testError5() {
	const times = 100_0000
	t1 := time.Now()
	for i := 0; i < times; i++ {
		recoverPanic()
	}
	du1 := time.Since(t1)


	t2 := time.Now()
	for i := 0; i < times; i++ {
		checkError()
	}
	du2 := time.Since(t2)
	
	fmt.Println("panic recover use time:", du1)
	fmt.Println("check error use time:", du2)
}

运行耗时,此处使用的是go 1.16,在go 1.14大幅度优化了defer的性能,因此panic-recover的运行速度并不会慢太多,在100万次的运行中,性能差距0.1945303s:

panic recover use time: 2.0697743s
check error use time: 1.875244s
  1. 自定义异常,go的异常其实是个接口,只要实现了以下方法,就可以当作一个异常:
type error interface {
	Error() string
}

go 的errors.New可以用来新建异常:

var MomoError = errors.New("this is error")

func welcomeUnlessMomo(name string) error {
	if name == "momo" {
		return MomoError
	} else if name == "sb" {
		msg := "no welcome sb"
		fmt.Println(msg)
		return errors.New(msg)
	} else {
		fmt.Println("welcome", name)
	}
	return nil
}

func testError6(name string) {
	if err := welcomeUnlessMomo(name); err != nil && err == MomoError {
		fmt.Println("hello", "momo")
	}
}

输出结果:

welcome 123
hello momo
no welcome sb

亦可以实现Error方法来自定义异常:


var MomoError = errors.New("this is error")

type SbError struct {
	name string
}

func (sb *SbError) Error() string {
	return "sb: " + sb.name
}

func welcomeUnlessMomo(name string) error {
	if name == "momo" {
		return MomoError
	} else if name == "sb" {
		msg := "no welcome sb"
		fmt.Println(msg)
		return errors.New(msg)
	} else if name == "fool" || name == "stupid" {
		msg := "find a sb:" + name
		fmt.Println(msg)
		return &SbError{name}
	} else {
		fmt.Println("welcome", name)
	}
	return nil
}


func testError7() {
	if err := welcomeUnlessMomo("fool"); err != nil {
		if _,isSb := err.(*SbError); isSb {
			fmt.Println("sb error")
		}
	}
}

  1. errors的错误支持包装和解包:
func testError8() {
	// unwrap
	err1 := errors.New("1")
	err2 := fmt.Errorf("2:%w", err1)
	err3 := fmt.Errorf("3:%w", err2)
	err4 := fmt.Errorf("4:%w", err3)

	fmt.Println(err4)
	fmt.Println(errors.Unwrap(err4))
	fmt.Println(errors.Unwrap(errors.Unwrap(err4)))
	fmt.Println(errors.Unwrap(errors.Unwrap(errors.Unwrap(err4))))
	fmt.Println(errors.Unwrap(errors.Unwrap(errors.Unwrap(errors.Unwrap(err4)))))
}

打印结果:

4:3:2:1
3:2:1
2:1
1
<nil>

  1. Is和 == 似乎区别不大,结果都是一致的,true false true false。异常并不是字符串,比较时不是值比较的。
func testError9() {
	err := MomoError
	err2 := errors.New("this is error")
	fmt.Println(errors.Is(err, MomoError))
	fmt.Println(errors.Is(err2, MomoError))
	fmt.Println(err == MomoError)
	fmt.Println(err2 == MomoError)
}

  1. 当error一层包一层时,怎么确定error具有某种类型?errors.As就是用来判断这个的:
func testError10() {
	err := MomoError
	err2 := fmt.Errorf("wrap:%w", err)
	fmt.Println(errors.As(err2, &err))
}

打印结果是true。

最后

go panic-recover不好吗?很多人不推荐使用。这里说一下我的看法。

于我而言,go的返回error确实是比较舒适的,因为这样可以满足我的强迫症,细粒度的判断并解决每一个错误,相比panic的集中recover,在异常发生之后就决定要怎么做,我觉得会更恰当。panic是不可见的,而error返回值是总可见的。如果你调用的函数内部发生了panic,但是其内部并没有recover,而是抛到了外层,而你又没有去recover,那么这个panic就会导致你的代码运行失败,终止退出。我想,这是不推荐使用panic的最主要的原因之一。

但是,return error如果滥用也会造成像js语言promise.then那样的嵌套地狱。panic提供了一个直达上游的方法,这在一些需要进行深层次的业务逻辑嵌套中中断代码提供了一个很有效的手段。如果没有panic,那么只能在函数的调用的每一层去判断:if err == ErrXXX {return }。这样的代码着实没有什么值得称道的。
对于panic,我觉得要分情况讨论:

  1. 你开发的是个公共的包,提供第三方调用,那么,请勿向公开方法外部抛出任何panic。你可以使用,但是需要记得recover。因为panic是外部难以感知的。调用方很可能没有recover你的panic,那么一旦panic,造成了应用终止,损失是很大的。
  2. 如果你开发的是顶层应用代码,也即是业务代码。那么建议在入口recover所有错误。这样可以避免遭遇异常panic。
  3. go1.14以来,refer的性能已经优化的很好了。如果能用panic节省代码量,方便统一抽象异常处理逻辑,那就用吧,千万不要陷入教条主义。

阅读更多go相关文章,您可以访问我的个人网站:www.hengyumo.cn

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-04-07 23:05:00  更:2022-04-07 23:06:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 4:22:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码