Go 专栏|错误处理:defer,panic 和 recover

简介: Go 专栏|错误处理:defer,panic 和 recover

QQ图片20220423132857.png

这篇来说说 Go 的错误处理。


错误处理


错误处理相当重要,合理地抛出并记录错误能在排查问题时起到事半功倍的作用。


Go 中有关于错误处理的标准模式,即 error 接口,定义如下:


type error interface {
  Error() string
}
复制代码


大部分函数,如果需要返回错误的话,基本都会将 error 作为多个返回值的最后一个,举个例子:


package main
import "fmt"
func main() {
  n, err := echo(10)
  if err != nil {
    fmt.Println("error: " + err.Error())
  } else {
    fmt.Println(n)
  }
}
func echo(param int) (int, error) {
  return param, nil
}
复制代码


我们也可以使用自定义的 error 类型,比如调用标准库的 os.Stat 方法,返回的错误就是自定义类型:


type PathError struct {
  Op   string
  Path string
  Err  error
}
func (e *PathError) Error() string {
  return e.Op + " " + e.Path + ": " + e.Err.Error()
}
复制代码


暂时看不懂也没有关系,等学会了接口之后,再回过头来看这段代码,应该就豁然开朗了。


defer


延迟函数调用,defer 后边会接一个函数,但该函数不会立刻被执行,而是等到包含它的程序返回时(包含它的函数执行了 return 语句、运行到函数结尾自动返回、对应的 goroutine panic),defer 函数才会被执行。


通常用于资源释放、打印日志、异常捕获等。


func main() {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    /**
     * 这里defer要写在err判断的后边而不是os.Open后边
     * 如果资源没有获取成功,就没有必要对资源执行释放操作
     * 如果err不为nil而执行资源执行释放操作,有可能导致panic
     */
    defer f.Close()
}
复制代码


defer 语句经常成对出现,比如打开和关闭,连接和断开,加锁和解锁。


defer 语句在 return 语句之后执行。


package main
import (
  "fmt"
)
func main() {
  fmt.Println(triple(4)) // 12
}
func double(x int) (result int) {
  defer func() {
    fmt.Printf("double(%d) = %d\n", x, result)
  }()
  return x + x
}
func triple(x int) (result int) {
  defer func() {
    result += x
  }()
  return double(x)
}
复制代码


切勿在 for 循环中使用 defer 语句,因为 defer 语句不到函数的最后一刻是不会执行的,所以下面这段代码很可能会用尽所有文件描述符。


for _, filename := range filenames {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()
}
复制代码


一种解决办法是将循环体单独写一个函数,这样每次循环的时候都会调用关闭函数。


for _, filename := range filenames {
    if err := doFile(filename); err != nil {
        return err
    }
}
func doFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()
}
复制代码


defer 语句的执行是按调用 defer 语句的倒序执行。


package main
import (
  "fmt"
)
func main() {
  defer func() {
    fmt.Println("first")
  }()
  defer func() {
    fmt.Println("second")
  }()
  fmt.Println("done")
}
复制代码


输出:


done
second
first
复制代码


panic 和 recover


一般情况下,在程序里记录错误日志,就可以帮助我们在碰到异常时快速定位问题。


但还有一些错误比较严重的,比如数组越界访问,程序会主动调用 panic 来抛出异常,然后程序退出。


如果不想程序退出的话,可以使用 recover 函数来捕获并恢复。


感觉挺不好理解的,但仔细想想其实和 try-catch 也没什么区别。


先来看看两个函数的定义:


func panic(interface{})
func recover() interface{}
复制代码


panic 参数类型是 interface{},所以可以接收任意参数类型,比如:


panic(404)
panic("network broken")
panic(Error("file not exists"))
复制代码


recover 需要在 defer 函数中执行,举个例子:


package main
import (
  "fmt"
)
func main() {
  G()
}
func G() {
  defer func() {
    fmt.Println("c")
  }()
  F()
  fmt.Println("继续执行")
}
func F() {
  defer func() {
    if err := recover(); err != nil {
      fmt.Println("捕获异常:", err)
    }
    fmt.Println("b")
  }()
  panic("a")
}
复制代码


输出:


捕获异常: a
b
继续执行
c
复制代码


F() 中抛出异常被捕获,G() 还可以正常继续执行。如果 F() 没有捕获的话,那么 panic 会向上传递,直接导致 G() 异常,然后程序直接退出。


还有一个场景就是我们自己在调试程序时,可以使用 panic 来中断程序,抛出异常,用于排查问题。


这个就不举例了,反正是我们自己调试,怎么爽怎么来就行了。


总结


错误处理在开发过程中至关重要,好的错误处理可以使程序更加健壮。而且将错误信息清晰地记录日志,在排查问题时非常有用。


Go 中使用 error 类型进行错误处理,还可以在此基础上自定义错误类型。


使用 defer 语句进行延迟调用,用来关闭或释放资源。


使用 panicrecover 来抛出错误和恢复。


使用 panic 一般有两种情况:


  1. 程序遇到无法执行的错误时,主动调用 panic 结束运行;


  1. 在调试程序时,主动调用 panic 结束运行,根据抛出的错误信息来定位问题。


为了程序的健壮性,可以使用 recover 捕获错误,恢复程序运行。



文章中的脑图和源码都上传到了 GitHub,有需要的同学可自行下载。


地址:github.com/yongxinz/go…

目录
相关文章
|
7天前
|
SQL 安全 数据库连接
《Go 简易速速上手小册》第6章:错误处理和测试(2024 最新版)(上)
《Go 简易速速上手小册》第6章:错误处理和测试(2024 最新版)
31 1
|
1月前
|
Go
Go语言中defer的执行顺序详解
【2月更文挑战第22天】
28 4
|
2月前
|
Go 开发者
Go语言中的错误处理与异常机制:实践与最佳策略
【2月更文挑战第7天】Go语言以其独特的错误处理机制而闻名,它鼓励显式错误检查而不是依赖于异常。本文将探讨错误处理与异常机制在Go语言中的实际应用,并分享一些最佳实践,帮助开发者编写更加健壮和易于维护的Go代码。
|
2月前
|
Go
Go语言中的异常处理:理解panic与recover
【2月更文挑战第7天】Go语言虽然以简洁和直接错误处理机制而著称,但它也提供了`panic`和`recover`这两个内置函数来处理程序中的异常情况。本文将深入探讨Go语言中的异常处理机制,包括`panic`和`recover`的使用场景、原理以及最佳实践,帮助读者更好地理解如何在Go中处理异常情况。
|
2月前
|
Go 开发者
Go语言错误处理机制:原理与实践
【2月更文挑战第7天】在Go语言中,错误处理是一项核心特性。Go语言鼓励显式的错误检查,而不是依赖于异常机制。本文将深入探讨Go语言的错误处理机制,包括错误的表示、传播和处理方式,以及如何在日常编程中有效地应用这一机制,确保代码的健壮性和可读性。
34 10
|
2月前
|
Go 开发者 UED
Go错误处理方式真的不好吗?
Go错误处理方式真的不好吗?
19 0
|
2月前
|
Go
开心档之 Go 错误处理
开心档之 Go 错误处理
|
3月前
|
网络协议 BI Go
Go-异常处理(defer recover panic)
Go-异常处理(defer recover panic)
31 0
|
3月前
|
程序员 Go 数据库
学习Go的错误处理机制及其应用场景
学习Go的错误处理机制及其应用场景
26 0