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
备注
本文系翻译之作原文博客地址
网友评论