美文网首页
Go教程第二十九篇:自定义错误

Go教程第二十九篇:自定义错误

作者: 大风过岗 | 来源:发表于2020-07-07 10:06 被阅读0次

    Custom Errors

    本文是《Go系列教程》的第二十九篇文章。
    在上篇文章中,我们学习了在Go中如何表示错误以及如何使用标准库处理错误。我们还学了如何提取更多的错误信息。本文主要讲述如何创建我们的自定义错误,以及使用和标准库相同的技术提取更多关于自定义错误的信息。

    使用New函数创建自定义错误

    创建自定义错误最简单的方法就是使用errors包下的New函数。在使用New函数创建自定义错误之前,我们要先弄清楚New函数的实现原理。
    New函数的实现如下:

           // Package errors implements functions to manipulate errors.
        package errors
    
        // New returns an error that formats as the given text.
        func New(text string) error {
            return &errorString{text}
        }
    
        // errorString is a trivial implementation of error.
        type errorString struct {
            s string
        }
    
        func (e *errorString) Error() string {
            return e.s
        }
    

    可以看出,其实现也相当简单。errorString是一个结构体类型,有一个字符串变量s。同时使用了一个errorString指针接收器实现了error接口。第五行的New函数接受一个string参数,并使用该参数创建了一个errorString类型的值,并返回此值的地址。因此,就这样创建了一个新的error并把它返回了。

    现在我们已经知道了New函数是如何工作的,下面我们就在自己的程序中使用它来创建一个自定义错误。

    我们将创建一个简单的程序,该程序会计算圆形的面积,并且会在半径为负数时,返回一个错误。

    程序代码如下:

    
    package main
    
    /**
       使用New函数创建自定义错误
     */
    import (
    
        "errors"
        "fmt"
        "math"
    )
    
    //radius是输入参数 radius
    //float64和error是返回值的类型,表示该函数会有俩个返回值:一个计算成功后的面积,一个是计算失败时,抛出的自定义错误
    func circleArea(radius float64) (float64,error){
        //如果半径小于零,就返回自定义错误
        if(radius <0){
            return 0,errors.New("Area calculation failed ,radius is less than zero")
        }
        //否则的话,就返回计算后的面积值和值为nil的error
        return math.Pi*radius*radius,nil;
    }
    
    
    func main(){
        radius  :=-20.0  //20.0
        area,err  :=circleArea(radius)
        if err !=nil{
            fmt.Println(err)
            return
        }
        fmt.Printf("Area of circle %0.2f",area)
    }
    
    

    在上面的程序中,我们检查了半径radius是否小于零,如果小于零的话,就返回相应的错误消息,如果比零大的话,就会计算面积的值,同时把面积的值和Nil返回。在main函数中,我们检查此函数的返回值error是否为Nil,如果不为Nil的话,我就把错误打印出来,并返回,否则的话,就打印圆形的面积。

    在这个程序中,radius是小于零的的,因此程序会输出如下:

    Area calculation failed, radius is less than zero
    

    使用Errorf向error中添加更多信息

    在上面的程序虽然已经可以返回一个自定义错误,但是如果我们想在错误信息中返回导致此错误的radius值,就不能够了。此时我们就用得着fmt包下的Errorf函数了。此函数可以根据一个格式指示符格式化此错误并返回字符串值。

    我们来用Errorf函数优化一下上面的那个程序:

    package main
    
    /**
        使用Errorf函数处理程序
     */
    
    import(
        "fmt"
        "math"
    )
    
    func circleArea(radius  float64) (float64,error){
    
        if(radius <0){
            return 0, fmt.Errorf("Area calculation failed ,radius %0.2f is less than zero",radius)
        }
        return math.Pi*radius*radius,nil;
    }
    
    
    func main(){
        radius :=-20.0
        area,err :=circleArea(radius)
        if err !=nil{
            fmt.Println(err)
            return
        }
        fmt.Printf("Area of circle %0.2f",area)
    }
    
    
    

    在上面的程序中,我们使用Errorf打印导致此错误的实际的radius的值。运行此程序,将输出如下:

    Area calculation failed, radius -20.00 is less than zero
    

    使用结构体类型和域字段提供更多的错误信息

    除了上面提到方式外,我们还可以使用实现了error接口的结构体来表示错误。这为我们在处理错误时提供了更多的灵活性。在我们的案例中,如果我们想访问导致此错误的radius变量。现在唯一的方式就是把上面这个错误的描述信息转换一下。但这并不是一个好的方式,因为如果描述信息变了的话,我们的代码也就崩了。

    我们将使用结构体的域字段来提供对radius的访问。我们会创建实现了error接口的结构体,并使用它的字段提供更多关于错误的信息。

    第一步,我们先创建一个代表此错误的结构体。针对error类型的命名约定,名字的后缀应以Error结尾。因此,我们就把我们的结构体命名为:areaError。

    type areaError struct {
        err    string
        radius float64
    }
    
    

    在上面的结构体中,有一个域字段radius,它存储了导致此错误的radius的值。err字段存储了实际的错误信息。

    下一步就是实现error接口:

    func (e *areaError) Error() string {
        return fmt.Sprintf("radius %0.2f: %s", e.radius, e.err)
    }
    
    

    在上面的代码片中,我们实现了error接口的Error() string方法。此方法会打印radius以及错误描述。

    我们完整此程序,如下:

    package main
    
    import (
        "fmt"
        "math"
    )
    
    //定义了一个结构体:areaError
    type areaError struct{
        err string
        radius float64
    }
    
    //areaError实现了Error接口
    func (e *areaError) Error() string{
        return fmt.Sprintf("radius %0.2f : %s ",e.radius,e.err)
    }
    
    func circleArea(radius float64)(float64,error){
        if radius <0 {
            //如果发生错误,就构造一个结构体返回
            return 0,&areaError{"radius is negative",radius}
        }
        return math.Pi*radius*radius,nil;
    }
    
    func main(){
        radius :=-20.0
        area,err :=circleArea(radius)
        if err!=nil{
            if err ,ok :=err.(*areaError); ok{
                fmt.Printf("Radius  %0.2f is less than zero",err.radius)
                return
            }
            fmt.Println(err)
            return
        }
        fmt.Printf("Area of rectangle %0.2f ",area)
    
    }
    
    

    在上面的程序中,circleArea被用于计算圆形的面积。函数会先检查radius是否小于零,如果是的话,它就会创建一个areaError类型的值,并把它的地址返回。
    这样的话,我们就提供了关于错误的更多信息,在本例中,导致此错误的radius会被放入到自定义错误结构体areaError的radius字段中。

    如果radius非负的话,函数会返回计算的面积值以及一个nil值的错误。

    在程序的第26行,当我们把radius的值置为-20时,由于此时半径radius的值小于零,故而就会返回一个错误。

    运行程序,所得输出如下:

    Radius -20.00 is less than zero
    

    使用结构体的方法提供更多的错误信息

    在本节中,我们会写一个程序计算矩形的面积,程序在长或宽为负数时,会打印一个错误。

    第一步,我们先创建一个表示此错误的结构体。

    type areaError struct {
        err    string //error description
        length float64 //length which caused the error
        width  float64 //width which caused the error
    }
    
    

    上面的这个结构体中,包含了错误描述以及导致此错误的长和宽的值。

    现在,我们以及有了一个错误类型,下面我们就来实现error接口,并在添加一组方法用于提供更多的错误信息。

    func (e *areaError) Error() string {
        return e.err
    }
    
    func (e *areaError) lengthNegative() bool {
        return e.length < 0
    }
    
    func (e *areaError) widthNegative() bool {
        return e.width < 0
    }
    
    

    在上面这个代码片中,我们从Error() string方法中返回了错误描述。lengthNegative() bool方法会在长度为负数时,返回true。
    widthNegative() bool方法会在宽度为负数时,返回true。这俩个方法都能提供更多关于错误的信息,在本例中,即是说,面积计算错误到底是由于长度为负数造成的,还是由于宽度为负数造成的。因此,我们就利用在areaError上定义更多的方法来提供关于错误的详细信息。

    下一步,就是计算面积函数:

    func rectArea(length, width float64) (float64, error) {
        err := ""
        if length < 0 {
            err += "length is less than zero"
        }
        if width < 0 {
            if err == "" {
                err = "width is less than zero"
            } else {
                err += ", width is less than zero"
            }
        }
        if err != "" {
            return 0, &areaError{err, length, width}
        }
        return length * width, nil
    }
    
    

    rectArea函数会检查长度或宽度是否小于零,如果是,则返回错误消息,否则的话,就返回矩形的面积,并一个nil值的error。

    创建一个main函数,如下:

    func main() {
        length, width := -5.0, -9.0
        area, err := rectArea(length, width)
        if err != nil {
            if err, ok := err.(*areaError); ok {
                if err.lengthNegative() {
                    fmt.Printf("error: length %0.2f is less than zero\n", err.length)
    
                }
                if err.widthNegative() {
                    fmt.Printf("error: width %0.2f is less than zero\n", err.width)
    
                }
                return
            }
            fmt.Println(err)
            return
        }
        fmt.Println("area of rect", area)
    }
    

    完整的程序如下:

    package main
    
    import "fmt"
    
    type areaError struct {
        err    string  //error description
        length float64 //length which caused the error
        width  float64 //width which caused the error
    }
    
    func (e *areaError) Error() string {
        return e.err
    }
    
    func (e *areaError) lengthNegative() bool {
        return e.length < 0
    }
    
    func (e *areaError) widthNegative() bool {
        return e.width < 0
    }
    
    func rectArea(length, width float64) (float64, error) {
        err := ""
        if length < 0 {
            err += "length is less than zero"
        }
        if width < 0 {
            if err == "" {
                err = "width is less than zero"
            } else {
                err += ", width is less than zero"
            }
        }
        if err != "" {
            return 0, &areaError{err, length, width}
        }
        return length * width, nil
    }
    
    func main() {
        length, width := -5.0, -9.0
        area, err := rectArea(length, width)
        if err != nil {
            if err, ok := err.(*areaError); ok {
                if err.lengthNegative() {
                    fmt.Printf("error: length %0.2f is less than zero\n", err.length)
    
                }
                if err.widthNegative() {
                    fmt.Printf("error: width %0.2f is less than zero\n", err.width)
    
                }
                return
            }
        }
        fmt.Println("area of rect", area)
    }
    

    运行此程序,输出如下:

    error: length -5.00 is less than zero
    error: width -9.00 is less than zero 
    

    备注
    本文系翻译之作原文博客地址

    相关文章

      网友评论

          本文标题:Go教程第二十九篇:自定义错误

          本文链接:https://www.haomeiwen.com/subject/lytaqktx.html