[golang] 错误处理

189 阅读3分钟
type error interface {
    Error() string
}

error 是一个内置的接口类型,很多内部包里面用到的 errorerrors 包下面实现的私有结构 errorString

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

可以通过 errors.New 把一个字符串转化为 errorString,以此得到一个满足 error 接口的对象。

// New returns an error that formats as the given text.
func New(text string) error {
    return &errorString{text}
}

为了更清晰的代码,应该总是使用包含错误值变量的 if 复合语句。

if value, err := pack1.Func1(param1); err != nil {
    fmt.Printf("Error %s in pack1.Func1 with parameter %v", err.Error(), param1)
    return // or: return err
} else {
    // Process(value)
}

定义错误

err := errors.New("math - square root of negative number")

在大部分情况下自定义错误结构类型很有意义的,可以包含除了(低层级的)错误信息以外的其它有用信息。

// PathError records an error and the operation and file path that caused it.
type PathError struct {
    Op   string // "open", "unlink", etc.
    Path string // The associated file.
    Err  error  // Returned by the system call.
}

func (e *PathError) Error() string {
    return e.Op + " " + e.Path + ": " + e.Err.Error()
}

错误判断

如果有不同错误条件可能发生,那么对实际的错误使用类型断言或类型判断(type-switch)是很有用的。

// err != nil
if e, ok := err.(*os.PathError); ok {
    // remedy situation
}

switch err := err.(type) {
case ParseError:
    PrintParseError(err)
case PathError:
    PrintPathError(err)
...
default:
    fmt.Printf("Not a special error, just %s\n", err)
}

需要注意的是,方法返回自定义错误时,返回值推荐设置为 error 类型,而非自定义错误类型,特别需要注意的是不应预声明自定义错误类型的变量。

func Decode() *SyntaxError {
    // 预声明错误变量
    var err *SyntaxError
    if 出错条件 {
        err = &SyntaxError{}
    }
    // 错误,err 永远等于非 nil,导致上层调用者 err != nil 的判断始终为 true
    return err
}

An interface value is nil only if the V and T are both unset, (T=nil, V is not set), In particular, a nil interface will always hold a nil type. If we store a nil pointer of type *int inside an interface value, the inner type will be *int regardless of the value of the pointer: (T=*int, V=nil). Such an interface value will therefore be non-nil even when the pointer value V inside is nil. 如果还需要更复杂的错误处理呢?

运行时异常和 panic

当发生像数组下标越界或类型断言失败这样的运行错误时,Go 运行时会触发运行时 panic,伴随着程序的崩溃抛出一个 runtime.Error 接口类型的值,这个错误值有个 RuntimeError() 方法用于区别普通错误。

从 panic 中 Recover

让程序可以从 panicking 重新获得控制权,停止终止过程进而恢复正常执行。

recover 只能在 defer 修饰的函数中使用,用于取得 panic 调用中传递过来的错误值,如果是正常执行,调用 recover 会返回 nil,且没有其它效果。

func protect(g func()) {
    defer func() {
        log.Println("done")
        // Println executes normally even if there is a panic
        if err := recover(); err != nil {
            log.Printf("run time panic: %v", err)
        }
    }()
    log.Println("start")
    g() //   possible runtime-error
}
import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.

自定义包中的错误处理和 panicking

在包内部,总是应该从 panicrecover,不允许显式的超出包范围的 panic(),向包的调用者返回错误,而不是 panic。

import (
    "fmt"
    "strconv"
    "strings"
)

// A ParseError indicates an error in converting a word into an integer.
type ParseError struct {
    Index int    // The index into the space-separated list of words.
    Word  string // The word that generated the parse error.
    Err   error  // The raw error that precipitated this error, if any.
}

// String returns a human-readable error message.
func (e *ParseError) String() string {
    return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word)
}

// Parse parses the space-separated words in in put as integers.
func Parse(input string) (numbers []int, err error) {
    defer func() {
        if r := recover(); r != nil {
            var ok bool
            err, ok = r.(error)
            if !ok {
                err = fmt.Errorf("pkg: %v", r)
            }
        }
    }()

    fields := strings.Fields(input)
    numbers = fields2numbers(fields)
    return
}

func fields2numbers(fields []string) (numbers []int) {
    if len(fields) == 0 {
        panic("no words to parse")
    }
    for idx, field := range fields {
        num, err := strconv.Atoi(field)
        if err != nil {
            panic(&ParseError{idx, field, err})
        }
        numbers = append(numbers, num)
    }
    return
}