zoukankan      html  css  js  c++  java
  • [golang note] 错误处理

    错误处理


    • 错误处理的标准模式

           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)
        })
    }
  • 相关阅读:
    TSQL 中游标应用示例
    [转]浅谈数据库设计技巧(上)、(下)
    ASP.NET页面打印技术的总结(转)
    深入理解RIA(转)
    三层架构的bussiness层没用?
    ASP.NET中常用的26个优化性能方法(转)
    基于MapX的GIS动态操作与实现
    web项目经理手册项目经理的工作内容(转)
    ASP.NET中上传文件到数据库
    学习.net中I/O的心得第一篇 初探I/O(转)
  • 原文地址:https://www.cnblogs.com/heartchord/p/5236091.html
Copyright © 2011-2022 走看看