Go学习笔记13-错误处理

什么是错误

在Go中,错误一直是很常见的,错误用内建的error类型来表示。如果一个函数或方法需要返回错误,按照惯例,会作为最后一个值返回。在处理错误时,通常都是将返回的错误与nil比较。nil值表示没有错误发生,而非nil值表示出现了错误。

错误类型的表示

error是一个接口类型,定义如下。所有实现该接口的类型都可以当作一个错误类型。Error()方法给出了错误的描述。

type error interface {
    Error() string
}

获取错误详细信息的各种方法

既然error是一个接口类型,那么它的底层就有对应的具体类型。通过error的具体类型能获取错误的更多信息。

1. 断言底层结构体类型,使用结构体字段获取更多信息

package main

import (
    "fmt"
    "os"
)

func main() {
    // doc中说明了 Open 函数返回的具体错误类型是 *PathError,PathError是
    // 结构体类型,通过声明 Error() string 方法,实现了 error 接口。
    file, err := os.Open("/test.txt")
    // 如果出现错误,使用类型断言获取 error 接口的底层值。
    if err, ok := err.(*os.PathError); ok {
        fmt.Println("File at path", err.Path, "failed to open") // 获取指定字段
        return
    }
    fmt.Println(file.Name(), "opened successfully")
    // Output: File at path /test.txt failed to open
}

2. 断言底层结构体类型,调用方法获取更多信息

package main

import (
    "fmt"
    "net"
)

func main() {
    // 试图获取域名 golangbot123.com 的 ip
    addr, err := net.LookupHost("golangbot123.com")
    if err, ok := err.(*net.DNSError); ok {
        if err.Timeout() {
            fmt.Println("operation timed out")
        } else if err.Temporary() {
            fmt.Println("temporary error")
        } else {
            fmt.Println("generic error:", err)
        }
        return
    }
    fmt.Println(addr)
    // Output: generic error: lookup golangbot123.com: no such host
}

3. 直接比较

还有一种获取错误更多信息的方式,是与error类型的变量直接比较。

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    // Glob 函数如果出错会返回一个错误 ErrBadPattern,
    // 而 filepath.ErrBadPattern 为包中定义的变量。
    // 所以可以直接进行比较。
    files, err := filepath.Glob("[")
    if err != nil && err == filepath.ErrBadPattern {
        fmt.Println(err)
        return
    }
    fmt.Println("matched files", files)
    // Output: syntax error in pattern
}

自定义错误

1. 使用标准库中的方法来创建自定义错误

  • 创建自定义错误最简单的方法是使用errors包中的New函数。
  • fmt包中的Errorf函数可以根据格式说明符,规定错误的格式,然后返回一个错误字符串。其实等同于errors.New(fmt.Sprintf(format, a...))

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

package main

import (
    "fmt"
    "math"
)

type areaError struct {
    err    string  // 存储了实际的错误信息
    radius float64 // 存储了与错误有关的半径
}

// 实现 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)
    // Output: Radius -20.00 is less than zero
}

3. 使用结构体类型的方法来提供错误的更多信息

package main

import "fmt"

type areaError struct {
    err    string  // 错误描述字段
    length float64 // 引发错误的长
    width  float64 // 引发错误的宽
}

// 实现 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(err)
        return
    }
    fmt.Println("area of rect", area)
    // Output:
    // error: length -5.00 is less than zero
    // error: width -9.00 is less than zero
}

reference:

https://studygolang.com/articles/12724
https://studygolang.com/articles/12784