Go-错误、异常处理详解

简介: Go-错误、异常处理详解

错误

1. type error interface {
2.     Error() string
3. }

内建error接口类型是约定用于表示错误信息,nil值表示无错误。

获取error信息

  _, err := os.Create("./.??")
  if err != nil{
    fmt.Printf("%v %T\n",err,err)
    fmt.Println(err.Error())
  }

结果

open ./.??: The filename, directory name, or volume label syntax is incorrect. *fs.PathError

open ./.??: The filename, directory name, or volume label syntax is incorrect.

源代码

src->os->error.go

type PathError = fs.PathError

src->io->fs->fs.go

type PathError struct {
  Op   string
  Path string
  Err  error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
func (e *PathError) Unwrap() error { return e.Err }
func (e *PathError) Timeout() bool {
  t, ok := e.Err.(interface{ Timeout() bool })
  return ok && t.Timeout()
}

PathError是结构体实现了error接口,可以看到结果和源代码的格式一样

error统一定义

errors包有

func New(text string) error

使用字符串创建一个错误


部分朋友一样的错误,每次都New,例如,errors.New("b can not be zero")、errors.New("division by zero")、errors.New("division by zero!!!")


我们应该学习go的开发者们的写法,将error统一定义,如果比较多,可以单独写一个文件(例如,errors.go)放在包中,比较少的话可以写在本文件

var divisionByZeroError = errors.New("division by zero")

error放在返回值类型列表的最后

约定的写法,一般都是放在最后,建议大家这样写

func division(a,b int) (int,error) {
  if b == 0{
    return 0,divisionByZeroError
  }
  return a/b,nil
}

多次尝试可避免失败,不必立即返回error

这个常见的情况就是写爬虫,由于网络原因,导致连接失败,一般情况下都是用户传一个重试次数retries,或者超时时间timeout,达到条件时才会返回错误或抛出异常。

多层嵌套,给error添加日志/出错位置

go的标准库的日志不是很强大,这里就不展示日志了,可以标出出错位置

func positionError(file,line string,err error) error {
  return errors.New(file+" "+ line + ":"+err.Error())
}

我们写一个正整数除法,调用前面的函数(有修改,查看全部代码)

func cal(a,b int) (int,error) {
  if a<=0 || b<0{
    return 0,positionError("err.go","28",negativeError)
  }
  return division(a,b)
}1.
  _, err = cal(3, 0)
  fmt.Println(err)
  _,err = cal(-3,3)
  fmt.Println(err)

结果

err.go 20:division by zero

err.go 28:calculate negative number

异常

有时程序出错是不可控的或很难判断的,如数组越界、空指针,这是就需要用到panic

panic

func panic(v interface

内建函数panic停止当前goroutine的正常执行。当函数F调用panic时,F的正常执行就会立刻停止。F中defer的所有函数先入后出执行后,F返回给其调用者G。G如同F一样行动,层层返回,直到该Go程中所有函数都按相反的顺序停止执行。之后,程序被终止,而错误情况会被报告,包括引发该panic的实参值,此终止序列称为panic过程。

// 使用panic
func cal2(a,b int) (int,error) {
  if a<=0 || b<0{
    panic("cannot use negative number")
  }
  return division(a,b)
}

结果

panic: cannot use negative number


goroutine 1 [running]:

main.cal2(0x1, 0xffffffffffffffff, 0xc0000d5f28, 0x1, 0x1)

       E:/Workspace/Go_workspace/learn_go/src/learnerr/main/err.go:36 +0x8a

main.main()

       E:/Workspace/Go_workspace/learn_go/src/learnerr/main/err.go:52 +0x2af

exit status 2

recover

有时异常是我们意料之外的,需要进行恢复,不影响后序程序的执行,这时,就需要recover

func recover() interface{}

内建函数recover允许程序管理panic过程中的goroutine。在defer的函数中,执行recover调用会取回传至panic调用的错误值,恢复正常执行,停止恐慌过程。若recover在defer的函数之外被调用,它将不会停止panic过程序列。在此情况下,或当该goroutine不在panic过程中时,或提供给panic的实参为nil时,recover就会返回nil。

func cal2(a,b int) (int,error) {
  if a<=0 || b<0{
    panic("cannot use negative number")
  }
  err := recover()
  fmt.Println(err)
  if err != nil{
    return 0,err.(error)
  }
  return division(a,b)
}
  res,err := cal2(1,-1)
  fmt.Println(res)

结果

panic: cannot use negative number


goroutine 1 [running]:

main.cal2(0x1, 0xffffffffffffffff, 0xc0000d5f28, 0x1, 0x1)

       E:/Workspace/Go_workspace/learn_go/src/learnerr/main/err.go:37 +0x185

main.main()

       E:/Workspace/Go_workspace/learn_go/src/learnerr/main/err.go:59 +0x2bb

exit status 2

可见,不在defer 的函数中使用recover是没有意义的

修改到defer的函数中

func cal2(a,b int) (int,error) {
  defer func() {
  if err := recover();err!=nil{
    fmt.Println(err)
    }
  }()
  if a<=0 || b<0{
    panic("cannot use negative number")
  }
  return division(a,b)
}

结果:

cannot use negative number

0

可以看到,虽然输出了panic函数中的话,但是没有panic,程序继续执行,输出了res

更详细的使用和细节查看:Go-关键字defer、panic、recover详解

全部代码

package main
import (
  "errors"
  "fmt"
  "os"
)
// error统一定义
var divisionByZeroError = errors.New("division by zero")
var negativeError = errors.New("calculate negative number")
// 记录错误发生的文件和函数
func positionError(file,line string,err error) error {
  return errors.New(file+" "+ line + ":"+err.Error())
}
// 除法函数
func division(a,b int) (int,error) {
  if b == 0{
    return 0,positionError("err.go","20",divisionByZeroError)
  }
  return a/b,nil
}
// 计算正整数除法
func cal(a,b int) (int,error) {
  if a<=0 || b<0{
    return 0,positionError("err.go","28",negativeError)
  }
  return division(a,b)
}
// 使用panic
func cal2(a,b int) (int,error) {
  defer func() {
    if err := recover();err!=nil{
      fmt.Println(err)
    }
  }()
  if a<=0 || b<0{
    panic("cannot use negative number")
  }
  //不在defer中使用recover是没有意义的
  //err := recover()
  //fmt.Println(err)
  //if err != nil{
  //  return 0,err.(error)
  //}
  return division(a,b)
}
func main()  {
  //-----------获取err信息------------
  _, err := os.Create("./.??")
  if err != nil{
    fmt.Printf("%v %T\n",err,err)
    fmt.Println(err.Error())
  }
  //----------嵌套error,标明位置
  _, err = cal(3, 0)
  fmt.Println(err)
  _,err = cal(-3,3)
  fmt.Println(err)
  res,err := cal2(1,2)
  fmt.Println(res)
  fmt.Println(fmt.Errorf("line %d, error:%s",69,negativeError))
}

运行截图

2020062310470442.png

总结

  • 错误是可控的,是程序定义的
  • 异常是未意料到的
  • 尽量使用error显式返回错误,而不是panic
  • error应该统一定义,添加位置等信息

参考

Go标准库-builtin

Go标准库-errors

-------------------------2021年5月29日 更新-----------------------------

fmt中有生成error的函数,

func Errorf(format string, a ...interface{}) error

Errorf根据format参数生成格式化字符串并返回一个包含该字符串的错误。

测试代码

  fmt.Println(fmt.Errorf("line %d, error:%s",69,negativeError))

结果

line 69, error:calculate negative number

已更新至全部代码


-------------------------2021年5月29日 更新结束-----------------------------


更多Go相关内容:Go-Golang学习总结笔记


有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。


相关文章
|
6月前
|
Rust 安全 程序员
|
1月前
|
Go
go基础-14.异常处理
go基础-14.异常处理
|
6月前
|
Go 开发者
Golang深入浅出之-Go语言 defer、panic、recover:异常处理机制
Go语言中的`defer`、`panic`和`recover`提供了一套独特的异常处理方式。`defer`用于延迟函数调用,在返回前执行,常用于资源释放。它遵循后进先出原则。`panic`触发运行时错误,中断函数执行,直到遇到`recover`或程序结束。`recover`在`defer`中捕获`panic`,恢复程序执行。注意避免滥用`defer`影响性能,不应对可处理错误随意使用`panic`,且`recover`不能跨goroutine捕获panic。理解并恰当使用这些机制能提高代码健壮性和稳定性。
130 2
|
6月前
|
Go 开发者
Go语言中的异常处理
【2月更文挑战第22天】
48 3
|
6月前
|
Go
Go语言中的异常处理:理解panic与recover
【2月更文挑战第7天】Go语言虽然以简洁和直接错误处理机制而著称,但它也提供了`panic`和`recover`这两个内置函数来处理程序中的异常情况。本文将深入探讨Go语言中的异常处理机制,包括`panic`和`recover`的使用场景、原理以及最佳实践,帮助读者更好地理解如何在Go中处理异常情况。
|
6月前
|
网络协议 BI Go
Go-异常处理(defer recover panic)
Go-异常处理(defer recover panic)
74 0
|
11月前
|
Go
go 异常处理
go 异常处理
38 1
|
Go
Go异常处理机制panic和recover
Go异常处理机制panic和recover
113 0
|
数据库连接 Go
Go基础:延迟调用defer、异常处理
Go基础:延迟调用defer、异常处理
590 1
Go基础:延迟调用defer、异常处理