在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更好,可能更多的是以开发者的编程习惯去决定是否在项目中使用全局捕获。