golang 的panic、defer、recover和error应用方式

2023年1月28日09:26:36

         异常处理在软件开发过程中是避不开的环节,各类高级语言都有自己的异常处理机制,例如:java的try-catch-finally、python的try-except 等,然而蛋疼的是golang并没有类似的捕获异常的方式,而是将异常和错误进行区分。所谓异常就是不在软件开发过程中所预料到的不正常行为,而错误是在开发过程中我们就可以预测到可能存在的业务上的问题,例如:在开发读取文件的程序过程中,开发人员认为这个文件不能不存在,但是很显然文件可能会不存在,因此这个不正常对与开发人员来说是一个异常;又例如:在执行一个除法计算的函数div(a,b float64),除数b如果是0是不应该被传递到函数中的,这个时候我们清楚的知道应该告诉调用者,你不应该传递0 ,所以在div函数中可以给调用者返回一个错误。异常和错误的处理在java中都可以用throw error 并且 进行try catch。而golang则提供了panic、recover、error的处理方式。

一、关于panic、defer和recover

    在golang中提供了一个类似java finally的做法,这个就是defer,defer 代码段是用于确保在函数运行之后仍然可以被执行的代码。例如:

package main
import "fmt"
import "errors"
func hello(a int) error{
    defer func() {
        fmt.Println("hello的最后执行")
    }()

    fmt.Println("step 1") 
    if a==0{
      errors.New("a不能为0")
    }
     
    fmt.Println("step 2")
    return nil
     
}
func main() {
   err:=hello(0)
   fmt.Printf( "hello返回:%v",err)
   
}

执行结果是:

step 1
hello的最后执行
hello返回:a不能为0

   

   golang的异常处理方式是panic()。 panic其实是制造一种系统宕机,任何的异常如果继续运行也许就已经偏离的程序的逻辑路线,所以也许宕机是最好的处理方式。但是go在宕机之前仍然会执行defer,也提供了系统恢复的可能,想要在panic之后仍然确保系统可以恢复运行,我们就需要利用recover。处理方式如下所示:

package main
import "fmt"
func main() {
    defer func() {
        if info := recover(); info != nil {
            fmt.Println("有人触发了宕机:", info)
        } else {
            fmt.Println("程序执行正常退出")
        }
    }()

    fmt.Println("step 1") 
    panic("我要宕机")

    fmt.Println("step 2")
    
    defer func() {
        fmt.Println("step 3")
    }()
}

 运行结果如下:

step 1
有人触发了宕机:我要宕机

二、关于errors   

errors 包是golang的错误的处理方式,可预测的错误都应该被返回和处理,例如:

func say(a,b string)(string, error){
   if b=="fuck"{
       return 0, errors.New("不能说脏话");
   }

   return fmt.Sprintf("%s:%s",a,b),nil 

 } 

    但是这样的错误处理方式在多层次的调用之后很容易因为不规范的开发或者不小心的处理遗漏。如果层层处理,到最后,我们回顾代码发现到处都是if err != nil { ...... }, 这真的很影响可读性也许这就是golang让人不爽的一点,当然我认为这些个缺点不影响我们使用golang这个优秀的语言。重要的是我们应该为error处理进行规范,以下是我在golang进行项目开发中总结的几点。

1、减少不必要的error使用

如果这个错误是可预测的并且只有一个,我们可以直接使用bool或者返回nil或者非nil进行区分。例如:

ipnet, ok := address.(*net.IPNet)
if !ok {
    // ipnet应用
}

 2、多个返回值时,把error放在最后一个

 3、建立错误信息常量,而不是随意使用

这么做有利于错误信息的判断、排错、提示。例如:

var ERR_CLOSED_PIPE = errors.New("io: read/write on closed pipe")
var ERR_NO_PROGRESS = errors.New("multiple Read calls return no data or error")
var ERR_SHORT_BUFFER = errors.New("short buffer")
var ERR_SHORT_WRITE = errors.New("short write")

4、多层次调用函数时,在每一层都日志输出

这样做有利于定位故障。

5、错误如果不影响系统运行,则不返回error

如果调用方不在呼是否执行出问题,函数就不要返回error了,免得调用方麻烦混淆使用。

6、为便于区分错误类型,可以统一定制错误类

      定义自己的错误类,然后使用.(type)进行区分处理。
     

以上是阶段性的思路,如有不正确的或遗漏,请见谅!

                 

  • 作者:Zone 7
  • 原文链接:https://blog.csdn.net/u012474395/article/details/120567957
    更新时间:2023年1月28日09:26:36 ,共 2144 字。