前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >To panic or not to panic

To panic or not to panic

作者头像
LA0WAN9
发布2021-12-14 08:47:46
4500
发布2021-12-14 08:47:46
举报
文章被收录于专栏:火丁笔记

大家都知道 Golang 推荐的错误处理的方式是使用 error,这主要得益于 Golang 方法可以返回多个值,我们可以很自然的用最后一个值来表示是否有错误,这一点是其它很多编程语言所不具备的,不过这多少让那些习惯了 exception 的程序员无所适从,虽然 Golang 没有 exception,但是实际上可以通过 panic/recover 来模拟出类似的效果,于是很多 Gopher 在错误处理的时候开始倾向于直接 panic。

为什么会有人喜欢使用 panic 来处理错误呢?我们以流行框架 Gin 为例来说明:

代码语言:javascript
复制
package main

import (
	"errors"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
)

func recovery() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Next()

		if err := c.Errors.Last(); err != nil {
			c.String(http.StatusInternalServerError, err.Error())
		}
	}
}

func main() {
	r := gin.Default()
	r.Use(recovery())

	r.GET("/", func(c *gin.Context) {
		if c.Query("foo") != "" {
			err := errors.New("foo error")

			c.Error(err)
			return
		}

		if c.Query("bar") != "" {
			err := errors.New("bar error")

			c.Error(err)
			return
		}

		c.String(http.StatusOK, "test")
	})

	r.Run(":8080")
}

在 Gin 的用法中,当出错的时候,应该先调用 c.Error 方法来设置 error,如果是在中间件里,那么应该调用 c.AbortWithError 方法,最后还要记得调用 return 返回,后续可以在中间件中通过判断 c.Errors 来决定如何渲染状态码和错误信息。很多人会觉得先 c.Error 再 return 的操作太麻烦,于是就出现了直接 panic 的做法:

代码语言:javascript
复制
package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

func recovery() gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				c.String(http.StatusInternalServerError, fmt.Sprint(err))
			}
		}()

		c.Next()
	}
}

func main() {
	r := gin.Default()
	r.Use(recovery())

	r.GET("/", func(c *gin.Context) {
		if c.Query("foo") != "" {
			panic("foo error")
		}

		if c.Query("bar") != "" {
			panic("bar error")
		}

		c.String(http.StatusOK, "test")
	})

	r.Run(":8080")
}

也就是说,当出错的时候,直接 panic 抛出异常,然后在中间件里通过 recover 里捕获异常,进而决定如何渲染错误信息,业务逻辑代码会更简洁明了。

如此说来,在 Golang 错误处理的时候,到底是应该使用 error 还是 panic 呢?之所有会有这样的疑问,很大程度上是因为我们混淆了错误和异常的区别:一个例子,当操作一个文件但是文件却不存在的时候,应该使用 error 而不是 panic,因为文件不存在可能在很多情况下新建一个就可以了,此时有药可救;另一个例子,当除数是零的时候,应该使用 panic 而不是 error,因为除数为零在数学上无意义,此时无药可救。

顺着这个思路,比如说在一个 MVC 架构的 Web 应用里,如果我们想在 controller 里报错,那么最终一般是展示一个定制化的错误页面,此时看上去属于无药可救的范畴,如此说来即便使用 panic 似乎也无可厚非,不过如果是在 model 之类可复用的组件中报错的话,除非真的真的无药可救,否则应该尽可能使用 error,毕竟你不可能指望别人在复用组件的时候还搭配着 recover 兜底。

此外,一旦在错误处理的时候滥用 panic,那么很可能会导致你忽略真正的 panic,比如当你的 Web 应用存在一个偶发崩溃问题的时候,而你却只是使用 panic/recover 渲染了一个错误页面,那么你很可能就错失这个问题了,当然,你可以在 recover 里记录日志信息,不过当你滥用 panic 的时候,即便记录日志信息,也会存在很多噪音,结局你很可能依然会错失真正有用的信息。问题的症结就在于混淆了错误和异常。

实际上,针对此类问题,Gin 作者有过相关的论述:Abort vs panic,Golang 官方博客中的文章也值得多读几遍:Error handling and GoErrors are values

综上所述,我们推荐 error 为主,panic 为辅。如果一定要 panic,最好是在 init 的时候 panic,毕竟一运行就看到挂掉比较容易发现并处理,对待 panic,务必要克制,它就像罂粟花,看似绚烂多彩,却隐藏着罪恶的果实,合理使用的话,有其自身价值,但千万别上瘾。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-03-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档