[翻译] effective go 之 Errors

Errors

Library routines must often return some sort of error indication to the caller. As mentioned earlier, Go's multivalue return makes it easy to return a detailed error description alongside the normal return value. By convention, errors have type error, a simple built-in interface.

库函数通常必须返回一些错误提示给调用者 正如之前提到的 Go函数支持多值返回 可以很容易地返回正常返回值的同时 返回详细的错误提示 错误有相应的类型error 它是一个内建的简单接口 如下:

type error interface {
    Error() string
}


A library writer is free to implement this interface with a richer model under the covers, making it possible not only to see the error but also to provide some context. For example, os.Open returns an os.PathError.

库的作者可以实现这个接口 并且添加更多的特性 以提供错误信息外的上下文环境 比如 os.Open返回os.PathError错误:

// PathError records an error and the operation and Path记录了这个错误 还有相关的操作 以及路径
// 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()
}

PathError's Error generates a string like this:

PathError的Error会生产一个如下的字符串:

open /etc/passwx: no such file or directory

Such an error, which includes the problematic file name, the operation, and the operating system error it triggered, is useful even if printed far from the call that caused it; it is much more informative than the plain "no such file or directory".

这个错误包含了产生错误的文件名 相应的操作 以及引发的操作系统错误 即使调用的层次比较多 还是有用的 总比“no such file or directory”更有意义吧


When feasible, error strings should identify their origin, such as by having a prefix naming the package that generated the error. For example, in package image, the string representation for a decoding error due to an unknown format is "image: unknown format".

如果有可能的话 错误提示字符串需要标记它们的出处 可以把产生错误的包的名字作为前缀 加在错误提示字符串前 比如 在image包中 由于未知格式导致的解码错误 它产生的错误提示字符串就是“image: unkown format"


Callers that care about the precise error details can use a type switch or a type assertion to look for specific errors and extract details. For PathErrors this might include examining the internal Err field for recoverable failures.

需要知道更多错误相关信息的调用者 可以使用类型switch或者类型断言来检查特定的错误 解析出更加详细的错误细节 对PathError来说 它可能会检查内部的Err字段 从而判断是否该错误可以恢复 或者做一些收尾工作

for try := 0; try < 2; try++ {
    file, err = os.Create(filename)
    if err == nil {
        return
    }
    if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC {
        deleteTempFiles()  // Recover some space. 释放存储空间
        continue
    }
    return
}

The second if statement here is idiomatic Go. The type assertion err.(*os.PathError) is checked with the "comma ok" idiom (mentioned earlier in the context of examining maps). If the type assertion fails, ok will be false, and e will be nil. If it succeeds, ok will be true, which means the error was of type *os.PathError, and then so is e, which we can examine for more information about the error.

上面代码中的第二个if语句是Go的使用习惯 使用”comma ok"来检查类型断言err.(*os.PathError)的结果 如果类型断言失败 ok的值为false e的值为nil 如果检查成功 ok的值为true 这意味着产生的错误是*os.PathError


Panic

The usual way to report an error to a caller is to return an error as an extra return value. The canonical Read method is a well-known instance; it returns a byte count and an error. But what if the error is unrecoverable? Sometimes the program simply cannot continue.

报告产生错误的通常做法是返回一个错误 Read方法就是比较好的例子 它返回读入的字节数 以及一个错误提示 但如果产生的错误是不可挽救的 那如何是好呢? 有些时候 产生错误会直接导致程序不可继续执行

For this purpose, there is a built-in function panic that in effect creates a run-time error that will stop the program (but see the next section). The function takes a single argument of arbitrary type—often a string—to be printed as the program dies. It's also a way to indicate that something impossible has happened, such as exiting an infinite loop. In fact, the compiler recognizes a panic at the end of a function and suppresses the usual check for a return statement.

为了解决这个问题 有个内建函数panic 它会创建运行时错误 迫使程序停止运行 panic接受一个任意类型的参数 通常是字符串告诉我们程序挂了 也可以用它来告知我们 某些不可能的事情发生了 比如 从死循环里退了出来 实际上 编译器在函数退出前识别panic 然后抑制了正常的返回语句检查


// A toy implementation of cube root using Newton's method.
func CubeRoot(x float64) float64 {
    z := x/3   // Arbitrary initial value
    for i := 0; i < 1e6; i++ {
        prevz := z
        z -= (z*z*z-x) / (3*z*z)
        if veryClose(z, prevz) {
            return z
        }
    }
    // A million iterations has not converged; something is wrong.
    panic(fmt.Sprintf("CubeRoot(%g) did not converge", x))
}

This is only an example but real library functions should avoid panic. If the problem can be masked or worked around, it's always better to let things continue to run rather than taking down the whole program. One possible counterexample is during initialization: if the library truly cannot set itself up, it might be reasonable to panic, so to speak.

这仅仅是一个例子 但是正真的库函数应该避免panic 如果产生的问题可以被忽略 或者是可以解决掉 那么继续执行程序总比挂了好 另一个反面例子是在初始化过程中产生的 如果库自身不能正确地执行 那么产生panic是合情合理的:

var user = os.Getenv("USER")

func init() {
    if user == "" {
        panic("no value for $USER")
    }
}


Recover

When panic is called, including implicitly for run-time errors such as indexing a slice out of bounds or failing a type assertion, it immediately stops execution of the current function and begins unwinding the stack of the goroutine, running any deferred functions along the way. If that unwinding reaches the top of the goroutine's stack, the program dies. However, it is possible to use the built-in function recover to regain control of the goroutine and resume normal execution.

当panic被调用后 它会立刻终止当前的程序执行 并且开始退出goroutine的栈 并且执行依次执行被defer的函数 如果这个过程到了最后一个goroutine的执行栈 程序也就挂了 但是 使用内建函数recover是可以处理panic 并且让程序继续执行

A call to recover stops the unwinding and returns the argument passed to panic. Because the only code that runs while unwinding is inside deferred functions, recover is only useful inside deferred functions.

调用recover会停止退栈 并且返回传递给panic的参数 因为panic后只有被defer的函数在跑 recover只在defer函数里有用


One application of recover is to shut down a failing goroutine inside a server without killing the other executing goroutines.

recover的一个应用场景是 关掉server内的一个产生错误的goroutine 但是不去影响到其它的goroutine

func server(workChan <-chan *Work) {
    for work := range workChan {
        go safelyDo(work)
    }
}

func safelyDo(work *Work) {
    defer func() {
        if err := recover(); err != nil {
            log.Println("work failed:", err)
        }
    }()
    do(work)
}

In this example, if do(work) panics, the result will be logged and the goroutine will exit cleanly without disturbing the others. There's no need to do anything else in the deferred closure; calling recover handles the condition completely.

上面这段代码 如果do(work)产生了panic 那么panic会被记录下了 对于的goroutine会退出 但是不会影响其它的goroutine defer函数并不需要做其它的事情 调用recover就ok了

Because recover always returns nil unless called directly from a deferred function, deferred code can call library routines that themselves use panic and recover without failing. As an example, the deferred function in safelyDo might call a logging function before calling recover, and that logging code would run unaffected by the panicking state.

由于在defer函数外调用recover总是返回nil 被defer的代码可以调用那些使用了panic和recover的库函数 举例来说 上面代码中的safelyDo可能会在调用recover之前调用logging函数 那么logging相关的代码可以不受当前panic的影响

With our recovery pattern in place, the do function (and anything it calls) can get out of any bad situation cleanly by calling panic. We can use that idea to simplify error handling in complex software. Let's look at an idealized excerpt from the regexp package, which reports parsing errors by calling panic with a local error type. Here's the definition of Error, an error method, and the Compile function.

知道了如何使用panic和recover后 do函数可以在遇到恶心的问题使用panic来退出执行 我们可以使用这个想法来简化复杂软件的错误处理 我们现在看一下理想化的regexp包中摘出来的代码片段 它通过调用panic来报告自己定义的错误 下面是Error error方法 和Compile函数的定义:

// Error is the type of a parse error; it satisfies the error interface.
type Error string
func (e Error) Error() string {
    return string(e)
}

// error is a method of *Regexp that reports parsing errors by
// panicking with an Error.
func (regexp *Regexp) error(err string) {
    panic(Error(err))
}

// Compile returns a parsed representation of the regular expression.
func Compile(str string) (regexp *Regexp, err error) {
    regexp = new(Regexp)
    // doParse will panic if there is a parse error.
    defer func() {
        if e := recover(); e != nil {
            regexp = nil    // Clear return value.
            err = e.(Error) // Will re-panic if not a parse error.
        }
    }()
    return regexp.doParse(str), nil
}

If doParse panics, the recovery block will set the return value to nil—deferred functions can modify named return values. It then will then check, in the assignment to err, that the problem was a parse error by asserting that it has the local type Error. If it does not, the type assertion will fail, causing a run-time error that continues the stack unwinding as though nothing had interrupted it. This check means that if something unexpected happens, such as an index out of bounds, the code will fail even though we are using panic and recover to handle user-triggered errors.

上面代码中 doParse如果产生了panic 恢复代码会把返回值设置为nil defer函数可以修改命名返回值  然后会在赋值给err的时间检查如果类型断言视频 会导致运行时错误 这会继续栈释放 这里的检查也以为着 如果发生了意外的情况 比如 index越界 就算我们使用panic和recover处理了用户产生的错误 那么这段代码还是会执行失败 

With error handling in place, the error method makes it easy to report parse errors without worrying about unwinding the parse stack by hand.

有了错误处理 error方法可以更加简单地报告解析错误 而不用去担心如何去处理解析失败后 释放栈空间的问题

Useful though this pattern is, it should be used only within a package. Parse turns its internal panic calls into error values; it does not expose panics to its client. That is a good rule to follow.

虽然这种方式很有用 但只应该在包内使用 Parse会在panic时 把panic转换成error值 它并不会让客户知道产生了panic 谨记

By the way, this re-panic idiom changes the panic value if an actual error occurs. However, both the original and new failures will be presented in the crash report, so the root cause of the problem will still be visible. Thus this simple re-panic approach is usually sufficient—it's a crash after all—but if you want to display only the original value, you can write a little more code to filter unexpected problems and re-panic with the original error. That's left as an exercise for the reader.

另外 这种再次产生panic的用法 如果真的错误发生了 会改变panic的值 好在原先的和新产生的错误都会在崩溃报告里出现 所以产生错误的根本原因还是可以找到的 于是 这个简单的再次产生panic的用法就足够用了 但如果你只想展示原来的错误值 那么你可以写代码 过滤掉非期望中的错误 然后用原先的错误再次产生panic

本文来自:开源中国博客

感谢作者:pengfei_xue

查看原文:[翻译] effective go 之 Errors

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。