Go 错误处理 - GO教程

Go 错误处理

Go 语言通过内置的错误接口提供了非常简单的错误处理机制。

Go 语言的错误处理采用显式返回错误的方式,而非传统的异常处理机制。这种设计使代码逻辑更清晰,便于开发者在编译时或运行时明确处理错误。

Go 的错误处理主要围绕以下机制展开:

  1. error 接口 :标准的错误表示。
  2. 显式返回值 :通过函数的返回值返回错误。
  3. 自定义错误 :可以通过标准库或自定义的方式创建错误。
  4. panic recover :处理不可恢复的严重错误。

error 接口

Go 标准库定义了一个 error 接口,表示一个错误的抽象。

error 类型是一个接口类型,这是它的定义:


type error interface {

    Error() string

}

  • 实现 error 接口 :任何实现了 Error() 方法的类型都可以作为错误。
  • Error() 方法返回一个描述错误的字符串。

使用 errors 包创建错误

我们可以在编码中通过实现 error 接口类型来生成错误信息。

创建一个简单错误:

示例代码
packagemainimport("errors""fmt")funcmain(){err:=errors.New("this is an error")fmt.Println(err)// 输出:this is an error}

函数通常在最后的返回值中返回错误信息,使用 errors.New 可返回一个错误信息:


func Sqrt(f float64) (float64, error) {

    if f < 0 {

        return 0, errors.New("math: square root of negative number")

    }

    // 实现

}

在下面的例子中,我们在调用 Sqrt 的时候传递的一个负数,然后就得到了 non-nil 的 error 对象,将此对象与 nil 比较,结果为 true,所以 fmt.Println(fmt 包在处理 error 时会调用 Error 方法)被调用,以输出错误,请看下面调用的示例代码:


result, err:= Sqrt(-1)



if err != nil {

   fmt.Println(err)

}


显式返回错误

Go 中,错误通常作为函数的返回值返回,开发者需要显式检查并处理。

显式返回错误:

示例代码
packagemainimport("errors""fmt")funcdivide(a,bint)(int,error){ifb==0{return0,errors.New("division by zero")}returna/b,nil}funcmain(){result,err:=divide(10,0)iferr!=nil{fmt.Println("Error:",err)}else{fmt.Println("Result:",result)}}

输出:


Error: division by zero

自定义错误

通过定义自定义类型,可以扩展 error 接口。

自定义错误类型:

示例代码
packagemainimport("fmt")typeDivideErrorstruct{DividendintDivisorint}func(e*DivideError)Error()string{returnfmt.Sprintf("cannot divide %d by %d",e.Dividend,e.Divisor)}funcdivide(a,bint)(int,error){ifb==0{return0,&DivideError{Dividend:a,Divisor:b}}returna/b,nil}funcmain(){_,err:=divide(10,0)iferr!=nil{fmt.Println(err)// 输出:cannot divide 10 by 0}}

fmt 包与错误格式化

fmt 包提供了对错误的格式化输出支持:

  • %v :默认格式。
  • %+v :如果支持,显示详细的错误信息。
  • %s :作为字符串输出。
示例代码
packagemainimport("fmt")// 定义一个 DivideError 结构typeDivideErrorstruct{divideeintdividerint}// 实现 `error` 接口func(de*DivideError)Error()string{strFormat:=`Cannot proceed, the divider is zero.dividee: %ddivider: 0`returnfmt.Sprintf(strFormat,de.dividee)}// 定义 `int` 类型除法运算的函数funcDivide(varDivideeint,varDividerint)(resultint,errorMsgstring){ifvarDivider==0{dData:=DivideError{dividee:varDividee,divider:varDivider,}errorMsg=dData.Error()return}else{returnvarDividee/varDivider,""}}funcmain(){// 正常情况ifresult,errorMsg:=Divide(100,10);errorMsg==""{fmt.Println("100/10 = ",result)}// 当除数为零的时候会返回错误信息if_,errorMsg:=Divide(100,0);errorMsg!=""{fmt.Println("errorMsg is: ",errorMsg)}}

执行以上程序,输出结果为:


100/10 =  10

errorMsg is:  

    Cannot proceed, the divider is zero.

    dividee: 100

    divider: 0




使用 errors.Is 和 errors.As

从 Go 1.13 开始, errors 包引入了 errors.Is errors.As 用于处理错误链:

errors.Is

检查某个错误是否是特定错误或由该错误包装而成。

示例代码
packagemainimport("errors""fmt")varErrNotFound=errors.New("not found")funcfindItem(idint)error{returnfmt.Errorf("database error: %w",ErrNotFound)}funcmain(){err:=findItem(1)iferrors.Is(err,ErrNotFound){fmt.Println("Item not found")}else{fmt.Println("Other error:",err)}}

errors.As

将错误转换为特定类型以便进一步处理。

示例代码
packagemainimport("errors""fmt")typeMyErrorstruct{CodeintMsgstring}func(e*MyError)Error()string{returnfmt.Sprintf("Code: %d, Msg: %s",e.Code,e.Msg)}funcgetError()error{return&MyError{Code:404,Msg:"Not Found"}}funcmain(){err:=getError()varmyErr*MyErroriferrors.As(err,&myErr){fmt.Printf("Custom error - Code: %d, Msg: %s\n",myErr.Code,myErr.Msg)}}

panic 和 recover

Go 的 panic 用于处理不可恢复的错误,recover 用于从 panic 中恢复。

panic:

  • 导致程序崩溃并输出堆栈信息。
  • 常用于程序无法继续运行的情况。

recover:

  • 捕获 panic ,避免程序崩溃。
示例代码
packagemainimport"fmt"funcsafeFunction(){deferfunc(){ifr:=recover();r!=nil{fmt.Println("Recovered from panic:",r)}}()panic("something went wrong")}funcmain(){fmt.Println("Starting program...")safeFunction()fmt.Println("Program continued after panic")}

执行以上代码,输出结果为:

Starting program...

Recovered from panic: something went wrong

Program continued after panic