[翻譯] effective go 之 Errors

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

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 stringfunc (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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.