IT业界:Go语言中的错误处理及资源管理

    作者:python君2020-05-12 17:57:44

    错误处理

    Go的语法接近C语言,但对于变量的声明有所不同。Go支持垃圾回收功能。Go的并行模型是以东尼·霍尔的通信顺序进程(CSP)为基础,采取类似模型的其他语言包括Occam和Limbo,但它也具有Pi运算的特征,比如通道传输。在1.8版本中开放插件(Plugin)的支持,这意味着现在能从Go中动态加载部分函数。

    接下来介绍Go语言中的错误处理,这是一个程序员最基本的功夫。

    defer延迟语句

    在介绍defer延迟语句之前,我们先来看一段非常简单的代码:

    package main

     

     

    import "fmt"

     

     

    func trydefer(){

     

        fmt.Println("1")

     

        fmt.Println("2")

     

        fmt.Println("3")

     

    }

     

     

    func main() {

     

        trydefer()

     

    }

     

    //运行结果:

     

    1

     

    2

     

    3

     

    现在我们将输出“1”的语句修改为defer fmt.Println("1"),发现输出结果为2 3 1;接着在将输出“2”的语句修改为defer fmt.Println("2"),再来看看输出结果3 2 1;如果全部前面都添加defer呢?猜一下输出结果:3 2 1。

    因此我们知道defer延迟语句确保调用在函数结束时发生,也就是说函数运行到defer语句处会跳过当前语句,等该函数执行完毕后,按照先进后出的顺序执行defer语句。

    延迟语句被用于执行一个函数调用,在这个函数之前,延迟语句返回。参数在defer语句时计算,defer列表为先进后出或者是后进先出。

    defer语句碰到panic,return,error等情况也能正常执行:

    func trydefer(){

     

        defer fmt.Println("1")

     

        defer fmt.Println("2")

     

        fmt.Println("3")

     

        return

     

        fmt.Println("4")  //这个代码不会执行,因为已经return了

     

    }

     

     

    func main() {

     

        trydefer()

     

    }

     

     

    //运行结果:

     

    3 2 1

     

    再来看一下panic:

    func trydefer(){

     

        defer fmt.Println("1")

     

        defer fmt.Println("2")

     

        fmt.Println("3")

     

        panic("程序运行到这里肯定会出错")

     

        fmt.Println("4")

     

    }

     

     

    func main() {

     

        trydefer()

     

    }

     

     

    //运行结果:

     

    3 2 1 panic: 程序运行到这里肯定会出错

     

    但是一般都是在写文件的时候才会采用defer防止自己忘记关闭连接,关闭文件,释放资源等。新建一个fib文件,接着在里面新建一个fib.go文件,里面写入闭包实现输出斐波那契数列的函数:

    package fib

     

     

    //闭包

     

    func Fptest()func()int{

     

        a, b:=0,1

     

        return func() int {

     

            a,b = b,a+b

     

            return a

     

        }

     

    }

     

    然后回到main包下的main.go文件,里面写入以下代码:

    package main

     

     

    import (

     

        "bufio"

     

        "fib"

     

        "fmt"

     

        "os"

     

    )

     

    func Writefile(filename string)  {

     

        file ,err := os.Create(filename)

     

        if err!=nil{

     

            panic(err)

     

        }

     

        defer file.Close()  //关闭文件

     

     

        //这样写的话文件速度较慢,可以先写到内存中,然后一次性写入文件中,下面就是这个代码

     

        writer:=bufio.NewWriter(file)

     

        defer writer.Flush()   //记得将数据从内存推入文件中,否则输出的数据仅仅在内存中

     

        f:= fib.Fptest()

     

        for i:=0;i<5;i++{

     

            fmt.Fprintln(writer,f())

     

        }

     

     

    }

     

     

    func main() {

     

        Writefile("fib.txt")

     

    }

     

    运行该文件后,发现"fib.txt"文件中已经写入1 1 2 3 5了。

    错误处理

    人工处理

    前面我们都是使用panic来输出错误error,但是并没有处理错误,下面我们就来尝试处理一下错误。在上面写文件的时候,我们使用了os.Create(filename)这个方法,查看一下它的源码:

    // Create creates the named file with mode 0666 (before umask), truncating

     

    // it if it already exists. If successful, methods on the returned

     

    // File can be used for I/O; the associated file descriptor has mode

     

    // O_RDWR.

     

    // If there is an error, it will be of type *PathError.

     

    func Create(name string) (*File, error) {

     

        return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)

     

    }

     

    看到没它其实是会返回很多信息的,我们尝试修改一下之前的代码,将file ,err := os.Create(filename)这句修改为file ,err := os.OpenFile(filename,os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0666)。然后运行一下,会发现出了问题,报以下错误:

    panic: open fib.txt: The file exists.

     

    既然知道会报错,因此可以不再输出panic(err),而是使用下面这种形式的代码,这样程序可以正常执行,而不会因为错误而停止运行:

    if err!=nil{

     

            fmt.Println("The file exists.")

     

            return

     

        }

     

    这样其实前提是已经知道发生了什么错误,如果不知道可以直接查看error本身的源码,发现它是一个接口:

    // The error built-in interface type is the conventional interface for

     

    // representing an error condition, with the nil value representing no error.

     

    type error interface {

     

        Error() string

     

    }

     

    既然是接口,那就可以直接拿来使用了,直接输入fmt.Println("err:",err.Error()),再次运行一下发现结果真的输出了错误:err: open fib.txt: The file exists.。

    这个代码其实是一下子全部把错误信息输出来了,前面在看Create方法源码的时候,你是否注意到了一点:If there is an error, it will be of type *PathError.如果出现一个错误,那它将会是*PathError,也就是多种PathError类型。使用前面介绍的动态类型检查错误的具体类型,然后再一次修改错误处理的代码:

    if err !=nil{

     

            if pathError,ok :=err.(*os.PathError);!ok{

     

                panic(err)

     

            }else{

     

                fmt.Printf("%s,%s,%s\\n",pathError.Op,pathError.Path,pathError.Err)

     

                //op是操作,path是路径,err是错误

     

            }

     

        }

     

    //运行结果:

     

    open,fib.txt,The file exists.

     

    当然你还可以自己定义error,使用如下方法:err=errors.New("这是自定义的error"),然后程序运行就会触发panic,进而导致程序崩溃,因为自己定义的error不是os.PathError类型。

    简单总结一下错误类型的表示,它是一个接口,因此任何实现该接口的类型都可以作为一个错误,进而调用该方法实现对错误的描述。

    type error interface {

     

        Error() string

     

    }

     

    统一错误处理

    现在又这么一个场景,100台服务器同时出现了某个错误,那么我们应该怎样编写高可复用的代码呢?往下看你就知道了。

    error和panic区别 : 意料之中用error,如文件打不开;意料之外用panic,如数组越界。

    一般在下列情况使用defer:Open/Close;Lock/Unlock;PrintHeader/PrintFooter等情况。

    与C++相比,Go并不包括如枚举、异常处理、继承、泛型、断言、虚函数等功能,但增加了 切片(Slice) 型、并发、管道、垃圾回收、接口(Interface)等特性的语言级支持。Go 2.0版本将支持泛型,对于断言的存在,则持负面态度,同时也为自己不提供类型继承来辩护。

课课家教育

未登录