Go 错误处理
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
Go 语言的错误处理采用显式返回错误的方式,而非传统的异常处理机制。这种设计使代码逻辑更清晰,便于开发者在编译时或运行时明确处理错误。
Go 的错误处理主要围绕以下机制展开:
error接口:标准的错误表示。- 显式返回值:通过函数的返回值返回错误。
- 自定义错误:可以通过标准库或自定义的方式创建错误。
panic和recover:处理不可恢复的严重错误。
error 接口
Go 标准库定义了一个 error 接口,表示一个错误的抽象。
error 类型是一个接口类型,这是它的定义:
type error interface {
Error() string
}
- 实现
error接口:任何实现了Error()方法的类型都可以作为错误。 Error()方法返回一个描述错误的字符串。
使用 errors 包创建错误
我们可以在编码中通过实现 error 接口类型来生成错误信息。
创建一个简单错误:
实例
package main
import (
"errors"
"fmt"
)
func main() {
err := errors.New("this is an error")
fmt.Println(err) // 输出:this is an error
}
import (
"errors"
"fmt"
)
func main() {
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 中,错误通常作为函数的返回值返回,开发者需要显式检查并处理。
显式返回错误:
实例
package main
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}
输出:
Error: division by zero
自定义错误
通过定义自定义类型,可以扩展 error 接口。
自定义错误类型:
实例
package main
import (
"fmt"
)
type DivideError struct {
Dividend int
Divisor int
}
func (e *DivideError) Error() string {
return fmt.Sprintf("cannot divide %d by %d", e.Dividend, e.Divisor)
}
func divide(a, b int) (int, error) {
if b == 0 {
return 0, &DivideError{Dividend: a, Divisor: b}
}
return a / b, nil
}
func main() {
_, err := divide(10, 0)
if err != nil {
fmt.Println(err) // 输出:cannot divide 10 by 0
}
}
import (
"fmt"
)
type DivideError struct {
Dividend int
Divisor int
}
func (e *DivideError) Error() string {
return fmt.Sprintf("cannot divide %d by %d", e.Dividend, e.Divisor)
}
func divide(a, b int) (int, error) {
if b == 0 {
return 0, &DivideError{Dividend: a, Divisor: b}
}
return a / b, nil
}
func main() {
_, err := divide(10, 0)
if err != nil {
fmt.Println(err) // 输出:cannot divide 10 by 0
}
}
fmt 包与错误格式化
fmt 包提供了对错误的格式化输出支持:
%v:默认格式。%+v:如果支持,显示详细的错误信息。%s:作为字符串输出。
实例
package main
import (
"fmt"
)
// 定义一个 DivideError 结构
type DivideError struct {
dividee int
divider int
}
// 实现 `error` 接口
func (de *DivideError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 0
`
return fmt.Sprintf(strFormat, de.dividee)
}
// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error()
return
} else {
return varDividee / varDivider, ""
}
}
func main() {
// 正常情况
if result, errorMsg := Divide(100, 10); errorMsg == "" {
fmt.Println("100/10 = ", result)
}
// 当除数为零的时候会返回错误信息
if _, errorMsg := Divide(100, 0); errorMsg != "" {
fmt.Println("errorMsg is: ", errorMsg)
}
}
import (
"fmt"
)
// 定义一个 DivideError 结构
type DivideError struct {
dividee int
divider int
}
// 实现 `error` 接口
func (de *DivideError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 0
`
return fmt.Sprintf(strFormat, de.dividee)
}
// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error()
return
} else {
return varDividee / varDivider, ""
}
}
func main() {
// 正常情况
if result, 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
检查某个错误是否是特定错误或由该错误包装而成。
实例
package main
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
func findItem(id int) error {
return fmt.Errorf("database error: %w", ErrNotFound)
}
func main() {
err := findItem(1)
if errors.Is(err, ErrNotFound) {
fmt.Println("Item not found")
} else {
fmt.Println("Other error:", err)
}
}
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
func findItem(id int) error {
return fmt.Errorf("database error: %w", ErrNotFound)
}
func main() {
err := findItem(1)
if errors.Is(err, ErrNotFound) {
fmt.Println("Item not found")
} else {
fmt.Println("Other error:", err)
}
}
errors.As
将错误转换为特定类型以便进一步处理。
实例
package main
import (
"errors"
"fmt"
)
type MyError struct {
Code int
Msg string
}
func (e *MyError) Error() string {
return fmt.Sprintf("Code: %d, Msg: %s", e.Code, e.Msg)
}
func getError() error {
return &MyError{Code: 404, Msg: "Not Found"}
}
func main() {
err := getError()
var myErr *MyError
if errors.As(err, &myErr) {
fmt.Printf("Custom error - Code: %d, Msg: %s\n", myErr.Code, myErr.Msg)
}
}
import (
"errors"
"fmt"
)
type MyError struct {
Code int
Msg string
}
func (e *MyError) Error() string {
return fmt.Sprintf("Code: %d, Msg: %s", e.Code, e.Msg)
}
func getError() error {
return &MyError{Code: 404, Msg: "Not Found"}
}
func main() {
err := getError()
var myErr *MyError
if errors.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,避免程序崩溃。
实例
package main
import "fmt"
func safeFunction() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
panic("something went wrong")
}
func main() {
fmt.Println("Starting program...")
safeFunction()
fmt.Println("Program continued after panic")
}
import "fmt"
func safeFunction() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
panic("something went wrong")
}
func main() {
fmt.Println("Starting program...")
safeFunction()
fmt.Println("Program continued after panic")
}
执行以上代码,输出结果为:
Starting program... Recovered from panic: something went wrong Program continued after panic
若小叶
zth***@126.com
参考地址
这里应该介绍一下 panic 与 recover,一个用于主动抛出错误,一个用于捕获panic抛出的错误。
概念
panic 与 recover 是 Go 的两个内置函数,这两个内置函数用于处理 Go 运行时的错误,panic 用于主动抛出错误,recover 用来捕获 panic 抛出的错误。
panic有两种情况,一是程序主动调用,二是程序产生运行时错误,由运行时检测并退出。panic后,程序会从调用panic的函数位置或发生panic的地方立即返回,逐层向上执行函数的defer语句,然后逐层打印函数调用堆栈,直到被recover捕获或运行到最外层函数。panic不但可以在函数正常流程中抛出,在defer逻辑里也可以再次调用panic或抛出panic。defer里面的panic能够被后续执行的defer捕获。recover用来捕获panic,阻止panic继续向上传递。recover()和defer一起使用,但是defer只有在后面的函数体内直接被掉用才能捕获panic来终止异常,否则返回nil,异常继续向外传递。例子1
//以下捕获失败 defer recover() defer fmt.Prinntln(recover) defer func(){ func(){ recover() //无效,嵌套两层 }() }() //以下捕获有效 defer func(){ recover() }() func except(){ recover() } func test(){ defer except() panic("runtime error") }例子2
多个panic只会捕捉最后一个:
package main import "fmt" func main(){ defer func(){ if err := recover() ; err != nil { fmt.Println(err) } }() defer func(){ panic("three") }() defer func(){ panic("two") }() panic("one") }使用场景
一般情况下有两种情况用到:
若小叶
zth***@126.com
参考地址
GG
401***8@qq.com
if result, errorMsg := Divide(100, 10); errorMsg == "" { fmt.Println("100/10 = ", result) } if _, errorMsg := Divide(100, 0); errorMsg != "" { fmt.Println("errorMsg is: ", errorMsg) }等价于:
result, errorMsg := Divide(100,10) if errorMsg == ""{ fmt.Println("100/10 = ", result) } result, errorMsg = Divide(100,0) if errorMsg != ""{ fmt.Println("errorMsg is: ", errorMsg) }GG
401***8@qq.com
zhu波比
568***900@qq.com
fmt.Println 打印结构体的时候,会把其中的 error 的返回的信息打印出来。
type User struct { username string password string } func (p *User) init(username string ,password string) (*User,string) { if ""==username || ""==password { return p,p.Error() } p.username = username p.password = password return p,""} func (p *User) Error() string { return "Usernam or password shouldn't be empty!"} } func main() { var user User user1, _ :=user.init("",""); fmt.Println(user1) }结果:
zhu波比
568***900@qq.com
gibson1112
185***72536@163.com
个人多次试验,总结几点 panic,defer 和 recover。
package main import ( "fmt" ) func main() { fmt.Println("外层开始") defer func() { fmt.Println("外层准备recover") if err := recover(); err != nil { fmt.Printf("%#v-%#v\n", "外层", err) // err已经在上一级的函数中捕获了,这里没有异常,只是例行先执行defer,然后执行后面的代码 } else { fmt.Println("外层没做啥事") } fmt.Println("外层完成recover") }() fmt.Println("外层即将异常") f() fmt.Println("外层异常后") defer func() { fmt.Println("外层异常后defer") }() } func f() { fmt.Println("内层开始") defer func() { fmt.Println("内层recover前的defer") }() defer func() { fmt.Println("内层准备recover") if err := recover(); err != nil { fmt.Printf("%#v-%#v\n", "内层", err) // 这里err就是panic传入的内容 } fmt.Println("内层完成recover") }() defer func() { fmt.Println("内层异常前recover后的defer") }() panic("异常信息") defer func() { fmt.Println("内层异常后的defer") }() fmt.Println("内层异常后语句") //recover捕获的一级或者完全不捕获这里开始下面代码不会再执行 }代码执行的结果:
gibson1112
185***72536@163.com
朝阳群众
cha***ngquynzhong@gmail.com
这个例子不给力,我重写了一个:
package main import ( "fmt" ) // 自定义错误信息结构 type DIV_ERR struct { etype int // 错误类型 v1 int // 记录下出错时的除数、被除数 v2 int } // 实现接口方法 error.Error() func (div_err DIV_ERR) Error() string { if 0==div_err.etype { return "除零错误" }else{ return "其他未知错误" } } // 除法 func div(a int, b int) (int,*DIV_ERR) { if b == 0 { // 返回错误信息 return 0,&DIV_ERR{0,a,b} } else { // 返回正确的商 return a / b, nil } } func main() { // 正确调用 v,r :=div(100,2) if nil!=r{ fmt.Println("(1)fail:",r) }else{ fmt.Println("(1)succeed:",v) } // 错误调用 v,r =div(100,0) if nil!=r{ fmt.Println("(2)fail:",r) }else{ fmt.Println("(2)succeed:",v) } }朝阳群众
cha***ngquynzhong@gmail.com
k
815***442@qq.com
// 定义 `int` 类型除法运算的函数 func Divide(varDividee int, varDivider int) (result int, errorMsg string){ if varDivider == 0{ dData := DivideError{ dividee: varDividee, divider: varDivider, } errorMsg := dData.Error() return 0, errorMsg } else { return varDividee / varDivider, "" } }k
815***442@qq.com