美文网首页
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教程第二十九篇:自定义错误

    Custom Errors 本文是《Go系列教程》的第二十九篇文章。在上篇文章中,我们学习了在Go中如何表示错误以...

  • Go教程第十九篇: Channels

    Go教程第十九篇: Channels 本文是《Go系列教程》的第十九篇文章。 在上一篇文章中,我们讨论了并发是如何...

  • Go教程第二十八篇:错误处理

    Error Handling 本文是《Go系列教程》的第二十八篇文章。 什么是错误? 错误即是表明了在程序中发生了...

  • Go教程第十六篇: 接口-2

    Go教程第十六篇: 接口-2 本文是《Go系列教程》的第十六篇文章,本章我们将讲解接口的第二部分。 使用指针接收者...

  • Go 语言基础——错误处理

    学习目标 掌握错误处理 掌握自定义错误处理 掌握defer关键字的使用 错误处理 GO没有异常处理机制 Go语言引...

  • Go教程第五篇:常量

    Go教程第五篇:常量 本文是《Go系列教程》的第五篇 定义 Go里面的常量术语是用于表示固定值,例如: 5,-89...

  • go语言-error 简版记录

    go语言中错误分为error和panic error 1.错误变量 2.自定义错误类型 3.错误类型返回 pani...

  • Go教程第二十五篇:Go中的反射

    Go中的反射 本文是《Go系列教程》的第二十六篇文章。 反射是Go的高级特性之一。我将尽力讲解地简单些。 本教程具...

  • Go教程第七篇:Package

    Go教程第七篇:Package 本文是《Go系列教程》的第七篇文章。 什么是包、为什么使用包 ? 到目前为止,我们...

  • Go教程第二篇:helloworld

    hello world 这篇文章是我们的《Golang系列教程》的第二篇文章,如果要了解和安装GO请查看第一篇文章...

网友评论

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

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