在go开发过程中最常遇见的便是各种err!=nil错误判断,特别是java习惯了使用异常类处理,更会觉得go当中异常错误判断十分麻烦,因此本文中使用全局异常捕获结合自定义异常即可大大减少err判断,实现优雅的业务逻辑
一、全局异常捕获
通过 defer
和 recorder
实现异常捕获
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func main() { defer func() { if err := recover(); err != nil { fmt.Println("happen error") } }() num1 := 1 num2 := 0 fmt.Println("cal before") res := num1 / num2 fmt.Println("cal after") fmt.Println("cal res=", res) }
|
执行结果如下
1 2 3
| # go run main.go cal before happen error
|
二、如何在gin中使用
1.编写异常捕获中间件
1 2 3 4 5 6 7 8 9 10
| func ExceptionMiddleware(c *gin.Context) { defer func() { if err := recover(); err != nil { c.JSON(500, gin.H{"msg": "服务器发生错误xxxx"}) c.Abort() } }() c.Next() }
|
2.使用中间件
1 2 3 4 5 6 7 8 9 10 11 12
| func main(){ router:=gin.Default() router.use(ExceptionMiddleware)
router.GET("/hello",func(c *gin.Context){ num1 := 1 num2 := 0 var res = num1 / num2 }) }
|
3.验证
访问/hello地址将会得到如下结果
三、优化异常捕获
异常捕获将会拦截所有的错误,但是很显然有些可控错误可以简单返回错误提示,有些系统错误又希望服务器记录并做不同逻辑处理,所以本节仍然以gin为例,讲述如何在异常捕获之后做不同逻辑
1.模拟简单的业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func Login(c *gin.Context){ user,err:=GetUser(username,password) if err!=nil { c.JSON(500, gin.H{"msg": "用户未注册"}) } c.JSON(200,user) }
func GetUserById(username string,password string) user,error{ user,err := db.xxx if err!=nil { return nil,err } return err,nil }
|
2.自定义错误结构
1 2 3 4 5 6 7
| type WrapError struct { Code int `json:"code"` Msg string `json:"msg"` } func (err WrapError) Error() string { return err.Msg }
|
3.修改异常捕获中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| func ExceptionMiddleware(c *gin.Context) { defer func() { if err := recover(); err != nil { c.JSON(500, gin.H{"msg": errorToString(err)}) c.Abort() } }() c.Next() } func errorToString(err interface{}) string { switch v := err.(type) { case WrapError: return v.Msg case error: debug.PrintStack() log.Printf("panic: %v\n", v.Error()) return "服务器发生错误" default: return err.(string) } }
|
4.修改业务逻辑
可以看到控制层已经没用err判断了,当然由于模拟逻辑太简单,没有很直观突出优化效果,实际上在项目中运用会大大减少代码量,且让逻辑看起来更又可读性,让控制层只有方法调用,更多的将业务逻辑放到service中,在service中有逻辑问题可以直接使用 panic
抛出,异常捕获便会封装好错误展示给客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func Login(c *gin.Context){ user := GetUser(username,password) c.JSON(200,user) }
func GetUserById(username string,password string) user,error{ user,err := db.xxx if err!=nil { panic(WrapError({Msg:"账号或密码错误"})) } return err,nil }
|
5.最后
最后,并不是说一定得做全局异常捕获处理,也并不代表这种方法就一定比用if判断err更好,可能更多的是以开发者的编程习惯
去决定是否在项目中使用全局捕获。