下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

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

作者:python君     来源: 今日头条点击数:18150发布时间: 2020-05-12 17:57:44

标签: IT业界互联网编程语言

软考,您想通过吗?一次通过才是硬道理

错误处理

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版本将支持泛型,对于断言的存在,则持负面态度,同时也为自己不提供类型继承来辩护。