[golang note] 错误处理

电玩女神 2022-06-14 02:54 233阅读 0赞

错误处理
• 错误处理的标准模式
   golang错误处理的标准模式:error接口。
   golang函数如果要返回错误,规范上是将error作为多返回值中的最后一个,但这并非是强制要求。

▶ error接口

  1. type error interface { Error() string }

▶ 内置的error类型使用

▪ 语法如下

  1. func 函数名(参数列表) (返回值列表, err error) {
  2. // 函数体
  3. }

▪ 错误处理

  例如我们有一个这样的函数:

  1. func Foo(param int) (n int, err error) {
  2. // 函数体
  3. }

   调用函数时建议按如下方式处理错误:

▪ 示例如下

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. )
  6. func divide(dividend float64, divisor float64) (result float64, err error) {
  7. if divisor == 0 {
  8. return -1, errors.New("除数为0")
  9. }
  10. return dividend / divisor, nil
  11. }
  12. func main() {
  13. result, err := divide(1, 2)
  14. if err != nil {
  15. fmt.Println(err.Error())
  16. } else {
  17. fmt.Println("result =", result)
  18. }
  19. result, err = divide(1, 0)
  20. if err != nil {
  21. fmt.Println(err.Error())
  22. } else {
  23. fmt.Println("result =", result)
  24. }
  25. }

▶ 自定义error类型使用

  golang错误处理支持自定义的error类型,只需要为自定义error类型实现Error接口即可。

▪ 语法如下

  1. type CustomError struct {
  2. ...
  3. }
  4. func (e *CustomError) Error() string {
  5. // 函数体
  6. }

▪ 示例如下

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type MathError struct {
  6. Op string
  7. info string
  8. }
  9. func (e *MathError) Error() string {
  10. return "Math operation " + e.Op + " error : " + e.info
  11. }
  12. func divide(dividend float64, divisor float64) (result float64, err error) {
  13. if divisor == 0 {
  14. return -1, &MathError{
  15. "division", "divisor is zero"}
  16. }
  17. return dividend / divisor, nil
  18. }
  19. func main() {
  20. result, err := divide(1, 2)
  21. if err != nil {
  22. fmt.Println(err.Error())
  23. } else {
  24. fmt.Println("result =", result)
  25. }
  26. result, err = divide(1, 0)
  27. if err != nil {
  28. fmt.Println(err.Error())
  29. } else {
  30. fmt.Println("result =", result)
  31. }
  32. }

▪ 类型转换

  如果处理错误时需要获取详细信息,而不仅仅满足于打印一句错误信息,那就需要用到类型转换。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type MathError struct {
  6. Op string
  7. info string
  8. }
  9. func (e *MathError) Error() string {
  10. return "Math operation " + e.Op + " error : " + e.info
  11. }
  12. func divide(dividend float64, divisor float64) (result float64, err error) {
  13. if divisor == 0 {
  14. return -1, &MathError{
  15. "division", "divisor is zero"}
  16. }
  17. return dividend / divisor, nil
  18. }
  19. func main() {
  20. result, err := divide(1, 0)
  21. if err != nil {
  22. // error类型转换为*MathError指针,因为接口定义传入类型对象为*MathError指针
  23. // 如果接口定义时传入类型对象为MathError,那么这里的写法为err.(MathError)
  24. if e, ok := err.(*MathError); ok {
  25. fmt.Println(e.info)
  26. }
  27. } else {
  28. fmt.Println("result =", result)
  29. }
  30. }

资源释放
   在c++程序中,经常要注意内存指针、文件句柄、网络套接字等等资源的释放,特别需要注意其释放的时机。而golang使用defer
关键字和背后的内部机制简单地解决了资源释放的问题。

   1、 defer关键字能保证其后的代码能在函数退出前调用。
   2、一个函数中可以存在多个defer语句,需要注意的是defer语句的调用是遵照先进后出的原则,即最后一个defer语句将最先被执行。
  3、 可以在defer后加一个匿名函数来进行复杂的清理工作。

• 简单的清理工作
▶ 语法如下

  1. func 函数名(参数列表) (返回值列表) {
  2. ...
  3. // 资源申请
  4. defer 清理函数
  5. ...
  6. }

▶ 示例如下

  1. package main
  2. import (
  3. "io"
  4. "os"
  5. )
  6. func CopyFile(dst, src string) (w int64, err error) {
  7. srcFile, err := os.Open(src)
  8. if err != nil {
  9. return
  10. }
  11. defer srcFile.Close()
  12. dstFile, err := os.Create(dst)
  13. if err != nil {
  14. return
  15. }
  16. defer dstFile.Close()
  17. return io.Copy(dstFile, srcFile)
  18. }
  19. func main() {
  20. CopyFile("D:/2.txt", "D:/1.txt")
  21. }

▶ 先进后出规则

  1. package main
  2. import ( "fmt" ) func Test() { defer fmt.Println(1) defer fmt.Println(2) defer fmt.Println(3) } func main() { Test() }

• 复杂的清理工作
▶ 语法如下

  1. func 函数名(参数列表) (返回值列表) {
  2. ...
  3. // 资源申请
  4. defer func() {
  5. // 复杂的清理工作
  6. } ()
  7. ...
  8. }

▶ 示例如下

  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "os"
  6. )
  7. func CopyFile(dst, src string) (w int64, err error) {
  8. srcFile, err := os.Open(src)
  9. if err != nil {
  10. return
  11. }
  12. defer func() {
  13. fmt.Println("close file :", src)
  14. srcFile.Close()
  15. }()
  16. dstFile, err := os.Create(dst)
  17. if err != nil {
  18. return
  19. }
  20. defer func() {
  21. fmt.Println("close file :", dst)
  22. dstFile.Close()
  23. }()
  24. return io.Copy(dstFile, srcFile)
  25. }
  26. func main() {
  27. CopyFile("D:/2.txt", "D:/1.txt")
  28. }

异常处理
  一些高级语言中一般提供类似try…catch…finally…的语法,用于捕获异常。golang提供panic和recover两个关键字用于异常处理。

• panic
panic在golang中是一个内置函数,接收一个interface{}类型的值作为参数:

  1. func panic(interface{}) {
  2. ...
  3. }

  当一个函数执行过程中调用panic函数时,函数执行流程将立即终止,但panic之前的defer关键字延迟执行的语句将正常执行,之后该函数将返回到上层调用函数,并逐层向上执行panic流程,直至函数所属的goroutine中所有正在执行函数终止。错误信息将被报告,包括在调用panic()函数时传入的参数。下面用一个示例说明:

  1. package main
  2. import ( "fmt" ) func MyFunc1() { defer fmt.Println("MyFunc1 defer 1") panic("MyFunc1 panic test") defer fmt.Println("MyFunc1 defer 2") } func MyFunc2() { defer fmt.Println("MyFunc2 defer 1") MyFunc1() defer fmt.Println("MyFunc2 defer 2") } func main() { MyFunc2() }

程序输出如下:
      panic前的defer先输出了
这里写图片描述

• recover
  recover在golang中是一个内置函数,返回一个interface{}类型的值作为参数:

  1. func recover() interface{} {
  2. ...
  3. }

  panic函数触发后不会立即返回,而是先defer,再返回。如果defer的时候,有办法将panic捕获到,然后及时进行异常处理,并阻止panic传递,那处理机制就完善了。因此golang提供了recover内置函数,用于捕获panic并阻止其向上传递。需要注意的是,recover之后,逻辑并不会恢复到panic处,函数还是会在defer之后返回,但是所属goroutine将不会退出。

▶ 本层函数处理

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func MyFunc1() {
  6. defer func() {
  7. fmt.Println("MyFunc1 defer 1")
  8. if r := recover(); r != nil {
  9. fmt.Println("Runtime error caught :", r)
  10. }
  11. }()
  12. panic("MyFunc1 panic test")
  13. fmt.Println("MyFunc1 defer 2")
  14. }
  15. func MyFunc2() {
  16. defer fmt.Println("MyFunc2 defer 1")
  17. MyFunc1()
  18. defer fmt.Println("MyFunc2 defer 2")
  19. }
  20. func main() {
  21. MyFunc2()
  22. }

程序输出如下:
panic输出,在recover扑捉painc后的defer没输出
这里写图片描述

▶ 上层函数处理

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func MyFunc1() {
  6. defer fmt.Println("MyFunc1 defer 1")
  7. panic("MyFunc1 panic test")
  8. fmt.Println("MyFunc1 defer 2")
  9. }
  10. func MyFunc2() {
  11. defer func() {
  12. fmt.Println("MyFunc1 defer 2")
  13. if r := recover(); r != nil {
  14. fmt.Println("Runtime error caught :", r)
  15. }
  16. }()
  17. MyFunc1()
  18. defer fmt.Println("MyFunc2 defer 2")
  19. }
  20. func main() {
  21. MyFunc2()
  22. }

程序输出:
     recover扑捉到painc后的defer不会输出
这里写图片描述

• 模拟try…catch…语法
▶ 语法如下

  1. func Try(f func(), handler func(interface{})) {
  2. defer func() {
  3. if err := recover(); err != nil {
  4. handler(err)
  5. }
  6. }()
  7. f()
  8. }

▶ 示例如下

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func Try(f func(), handler func(interface{})) {
  6. defer func() {
  7. if err := recover(); err != nil {
  8. handler(err)
  9. }
  10. }()
  11. f()
  12. }
  13. func main() {
  14. Try(func() {
  15. panic("main panic")
  16. }, func(e interface{}) {
  17. fmt.Println(e)
  18. })
  19. }

来源:http://www.cnblogs.com/heartchord/p/5236091.html

发表评论

表情:
评论列表 (有 0 条评论,233人围观)

还没有评论,来说两句吧...

相关阅读

    相关 golang处理运行时错误

    Go语言的错误处理思想及设计包含以下特征: //一个可能造成错误的函数,需要返回值中返回一个错误接口(error),如果调用是成功的,错误接口将返回nil,否则返回错误