分享
  1. 首页
  2. 文章

[golang note] 错误处理

heartchord · · 2341 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

错误处理


• 错误处理的标准模式

golang错误处理的标准模式:error接口。

golang函数如果要返回错误,规范上是将error作为多返回值中的最后一个,但这并非是强制要求。

▶ error接口

type error interface {
 Error() string
}

▶ 内置的error类型使用

▪ 语法如下

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

▪ 错误处理

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

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

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

n, err := Foo(0)
if err != nil {
 // 错误处理
} else {
 // 使用返回值n
}

▪ 示例如下

package main
import (
 "errors"
 "fmt"
)
func divide(dividend float64, divisor float64) (result float64, err error) {
 if divisor == 0 {
 return -1, errors.New("除数为0")
 }
 return dividend / divisor, nil
}
func main() {
 result, err := divide(1, 2)
 if err != nil {
 fmt.Println(err.Error())
 } else {
 fmt.Println("result =", result)
 }
 result, err = divide(1, 0)
 if err != nil {
 fmt.Println(err.Error())
 } else {
 fmt.Println("result =", result)
 }
}

▶ 自定义error类型使用

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

▪ 语法如下

type CustomError struct {
 ...
}
func (e *CustomError) Error() string {
 // 函数体
}

▪ 示例如下

package main
import (
 "fmt"
)
type MathError struct {
 Op string
 info string
}
func (e *MathError) Error() string {
 return "Math operation " + e.Op + " error : " + e.info
}
func divide(dividend float64, divisor float64) (result float64, err error) {
 if divisor == 0 {
 return -1, &MathError{"division", "divisor is zero"}
 }
 return dividend / divisor, nil
}
func main() {
 result, err := divide(1, 2)
 if err != nil {
 fmt.Println(err.Error())
 } else {
 fmt.Println("result =", result)
 }
 result, err = divide(1, 0)
 if err != nil {
 fmt.Println(err.Error())
 } else {
 fmt.Println("result =", result)
 }
}

▪ 类型转换

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

package main
import (
 "fmt"
)
type MathError struct {
 Op string
 info string
}
func (e *MathError) Error() string {
 return "Math operation " + e.Op + " error : " + e.info
}
func divide(dividend float64, divisor float64) (result float64, err error) {
 if divisor == 0 {
 return -1, &MathError{"division", "divisor is zero"}
 }
 return dividend / divisor, nil
}
func main() {
 result, err := divide(1, 0)
 if err != nil {
 // error类型转换为*MathError指针,因为接口定义传入类型对象为*MathError指针
 // 如果接口定义时传入类型对象为MathError,那么这里的写法为err.(MathError)
 if e, ok := err.(*MathError); ok {
 fmt.Println(e.info)
 }
 } else {
 fmt.Println("result =", result)
 }
}

资源释放


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

defer关键字能保证其后的代码能在函数退出前调用。

一个函数中可以存在多个defer语句,需要注意的是defer语句的调用是遵照先进后出的原则,即最后一个defer语句将最先被执行

可以在defer后加一个匿名函数来进行复杂的清理工作。

• 简单的清理工作

▶ 语法如下

func 函数名(参数列表) (返回值列表) {
 ...
 // 资源申请
 defer 清理函数
 ...
}

▶ 示例如下

package main
import (
 "io"
 "os"
)
func CopyFile(dst, src string) (w int64, err error) {
 srcFile, err := os.Open(src)
 if err != nil {
 return
 }
 defer srcFile.Close()
 dstFile, err := os.Create(dst)
 if err != nil {
 return
 }
 defer dstFile.Close()
 return io.Copy(dstFile, srcFile)
}
func main() {
 CopyFile("D:/2.txt", "D:/1.txt")
}

▶ 先进后出规则

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

• 复杂的清理工作

▶ 语法如下

func 函数名(参数列表) (返回值列表) {
 ...
 // 资源申请
 defer func() {
 // 复杂的清理工作
 } ()
 ...
}

▶ 示例如下

package main
import (
 "fmt"
 "io"
 "os"
)
func CopyFile(dst, src string) (w int64, err error) {
 srcFile, err := os.Open(src)
 if err != nil {
 return
 }
 defer func() {
  fmt.Println("close file :", src)
  srcFile.Close()
 }()
dstFile, err :
= os.Create(dst) if err != nil { return } defer func() { fmt.Println("close file :", dst) dstFile.Close() }() return io.Copy(dstFile, srcFile) } func main() { CopyFile("D:/2.txt", "D:/1.txt") }

异常处理


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

• panic

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

func panic(interface{}) {
 ...
}

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

package main
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()
}

程序输出如下:

• recover

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

func recover() interface{} {
 ...
}

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

▶ 本层函数处理

package main
import (
 "fmt"
)
func MyFunc1() {
 defer func() {
  fmt.Println("MyFunc1 defer 1")
  if r := recover(); r != nil {
  fmt.Println("Runtime error caught :", r)
  }
 }()
 panic("MyFunc1 panic test")
 fmt.Println("MyFunc1 defer 2")
}
func MyFunc2() {
 defer fmt.Println("MyFunc2 defer 1")
 MyFunc1()
 defer fmt.Println("MyFunc2 defer 2")
}
func main() {
 MyFunc2()
}

程序输出如下:

▶ 上层函数处理

package main
import (
 "fmt"
)
func MyFunc1() {
 defer fmt.Println("MyFunc1 defer 1")
 panic("MyFunc1 panic test")
 fmt.Println("MyFunc1 defer 2")
}
func MyFunc2() {
 defer func() {
  fmt.Println("MyFunc1 defer 2")
  if r := recover(); r != nil {
  fmt.Println("Runtime error caught :", r)
  }
 }()
 MyFunc1()
 defer fmt.Println("MyFunc2 defer 2")
}
func main() {
 MyFunc2()
}

程序输出如下:

• 模拟try...catch...语法

▶ 语法如下

func Try(f func(), handler func(interface{})) {
 defer func() {
 if err := recover(); err != nil {
 handler(err)
 }
 }()
 f()
}

▶ 示例如下

package main
import (
 "fmt"
)
func Try(f func(), handler func(interface{})) {
 defer func() {
 if err := recover(); err != nil {
 handler(err)
 }
 }()
 f()
}
func main() {
 Try(func() {
 panic("main panic")
 }, func(e interface{}) {
 fmt.Println(e)
 })
}

有疑问加站长微信联系(非本文作者)

本文来自:博客园

感谢作者:heartchord

查看原文:[golang note] 错误处理

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
2341 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏