Go Language error handling

Source: Internet
Author: User
Tags exit in gopher switch case
This is a creation in Article, where the information may have evolved or changed.

Recent leisure use go to write a lib, which involves the error processing where I have been pondering for a long time. There are a lot of data and videos about Go error handling, and go authors have mentioned some tips and best practice on some go error handling in official articles and blogs, and this is just a collection and summary. As far as possible, if there are deficiencies, please add in the comments. (October for various reasons, no tillage bo, at the end of the month, hope is not late ^_^)

I. Overview

Go is a simple language, often come out to preach is as Gopher learned to be proud of only 25 key words ^_^. So go's error handling is as simple as ever. We know that C-language error handling is in the mainstream with the return error code (errno), and the current enterprise first language Java uses the try-catch-finally approach to deal with errors and anomalies (developers often misuse the mechanism because they don't know exactly what is wrong and which are exceptions). Go inherits C, with the return value being the primary way of error handling (with panic and recover coping with runtime exceptions). But unlike C, in the Go idiom, the return value is not a common return value type such as Integer, but instead uses an error (interface type).

type interface error {    Error() string}

This also embodies the "orthogonal" concept in Go philosophy: the separation of the error context from the error type. Regardless of whether the error context is int, float, or string or whatever, use error as the return value type.

func yourFunction(parametersList) (..., error)func (Receiver)yourMethod(parametersList) (..., error)

In Andrew Gerrand's "error handling and Go" article, one of the Go authors clarified that the error context was supply by the error interface implementation. In the Go standard library, go provides two ways to create a variable instance of the type that implements the error interface: errors. New and Fmt.errorf:

errors.New("your first error code")fmt.Errorf("error value is %d\n", errcode)

These two methods actually return the same type instance that implemented the error interface, the unexported type is errorstring. As the name implies, this error type only provides a string of context!

//$GOROOT/srcerrors/errors.gotype errorString struct {    s string}func (e *errorString) Error() string {    return e.s}

These two methods also basically satisfy the error-handling requirements in most of the daily learning and Development code.

Ii. Customary law (idiomatic usage)

1. Basic usage

Just like the function or method definition above:

func yourFunction(parametersList) (..., error)func (Receiver)yourMethod(parametersList) (..., error)

Typically, we define the last return value type in a function or method definition to be error. When using this function or method, the error code is determined as follows:

..., err := yourFunction(...)if err != nil {    //error handling}orif ..., err := yourFunction(...); err != nil {    //error handling}

2. Precautions

1), never ignore (ignore) The error code returned by the function or method, Check it. (Exception: Go code, including the standard library, rarely judges the return value of FMT.PRINTLN or printf series functions)

2), error, content format in string context: header lowercase, end without punctuation. Because of the fact that error is used so often:

... err := errors.New("error example")fmt.Printf("The returned error is %s.\n", err)

3), error processing flow indent style

Prefer

..., err := yourFunction(...)if err != nil {    // handle error}//go on doing something.

Rather than:

..., err := yourFunction(...)if err == nil {    // do something.}// handle error

Three, the groove point and the method of crack

Go from the day of the birth accompanied with great controversy, it is not surprising, like entertainment, if there is no controversy, there is a sense of existence, brush the face of the opportunity is not. It seems that controversy is a good thing, and the undisputed programming language has become history. Hype Understand! This is also a lot of Gopher Weibo, Twitter, medium account likes to send the "why I don't like go" article Reason for it ^_^.

Go's error handling is one of the points of criticism, the main argument is that Go's error handling mechanism seems to return to the 70 's (and C-age ^_^), making the error handling code lengthy and repetitive (partly because of the previous mentioned: do not ignore any error code), For example, some common error-handling code forms are as follows:

err := doStuff1()if err != nil {    //handle error...}err = doStuff2()if err != nil {    //handle error...}err = doStuff3()if err != nil {    //handle error...}

There's no way to refute these arguments, Russ Cox, one of the go authors, refutes this view: the error-handling mechanism of choosing a return value instead of Try-catch the mechanism, mainly considering that the former is suitable for large software, which is more suitable for small programs. When the program gets bigger, Try-catch will make the error handling verbose and cumbersome (see the Go FAQ for details). But Russ Cox also admits that the error handling mechanism of Go has a certain mental burden on developers.

Well, the narrative of this slot donuts, we are concerned about "how to crack"! Go's error handling is lengthy, but using some tips, or you can reduce the code to a tolerable range, here are a list of three:

1. CheckError Style

For some of the error handle, you can choose Goroutine Exit (Note: If you have only one goroutine in the main goroutine, call runtime. Goexit will cause program to exit in crash form) or os.exit, we can choose a common checkerror way to simplify error handling, such as:

func checkError(err error) {    if err != nil {        fmt.Println("Error is ", err)        os.Exit(-1)    }}func foo() {    err := doStuff1()    checkError(err)    err = doStuff2()    checkError(err)    err = doStuff3()    checkError(err)}

This approach is somewhat similar to the use of macro in C to simplify the error-handling process code, except that the go does not support macros, making this approach a limited range of applications.

2, aggregation error handle functions

There are times when we encounter this situation:

err := doStuff1()if err != nil {    //handle A    //handle B    ... ...}err = doStuff2()if err != nil {    //handle A    //handle B    ... ...}err = doStuff3()if err != nil {    //handle A    //handle B    ... ...}

In each error-handling process, the process is similar to handle a, handle B, etc., we can go through the defer + closure to provide the way to handle A, handle B ... Aggregated into a defer anonymous helper function:

func handleA() {    fmt.Println("handle A")}func handleB() {    fmt.Println("handle B")}func foo() {    var err error    defer func() {        if err != nil {            handleA()            handleB()        }    }()    err = doStuff1()    if err != nil {        return    }    err = doStuff2()    if err != nil {        return    }    err = doStuff3()    if err != nil {        return    }}

3. Bind Dostuff and Error processing

In Rob Pike's "Errors is Values" article, Rob Pike told uses this Trick,bufio with a trick writer that simplifies error-handling code in the US standard library:

    b := bufio.NewWriter(fd)    b.Write(p0[a:b])    b.Write(p1[c:d])    b.Write(p2[e:f])    // and so on    if b.Flush() != nil {            return b.Flush()        }    }

We see that the code does not judge three B. Write return error value, where is error handling placed? Let's open the $goroot/src/.

type Writer struct {    err error    buf []byte    n   int    wr  io.Writer}func (b *Writer) Write(p []byte) (nn int, err error) {    for len(p) > b.Available() && b.err == nil {        ... ...    }    if b.err != nil {        return nn, b.err    }    ......    return nn, nil}

As we can see, the error handling is bound to the inside of the Writer.write, and the writer definition has an err as an error state value, tied to the instance of writer, and at each write entry to determine whether it is nil. Once the!=nil,write actually did nothing, return.

The above three kinds of methods of cracking, each have the application of the scene, the same you can see each have their shortcomings, there is no universal law. The method of optimizing go error handling is not limited to the above three cases, there will certainly be more solution, such as code generation, such as others to be explored.

Iv. the confusion of the caller

The previous example is a simpler case for the caller. But in actual coding, the caller is not only confronted with:

if err != nil {    //handle error}

Also to face:

if err 是 ErrXXX    //handle errorXXXif err 是 ErrYYY    //handle errorYYYif err 是ErrZZZ    //handle errorZZZ

We have three scenarios to illustrate how callers handle different types of error implementations:

1, by errors. The error variable returned by new or Fmt.errorf

If you call a function or method that returns an error, the variable is called errors. New or Fmt.errorf, because the errorstring type is unexported, we cannot differentiate between values or types of different error variables by "fairly determined" or type assertion, type switch. The only way is to judge err. String () is equal to an error context string, the schematic code is as follows:

func openFile(name string) error {    if file not exist {        return errors.New("file does not exist")    }    if have no priviledge {        return errors.New("no priviledge")    }    return nil}func main() {    err := openFile("example.go")    if err.Error() == "file does not exist" {        // handle "file does not exist" error        return    }    if err.Error() == "no priviledge" {        // handle "no priviledge" error        return    }}

But this situation is too low, do not recommend this! Once a similar situation is encountered, it is necessary to consider the following method to reconstruct the above situation.

2. Exported Error variable

Open $goroot/src/os/error.go, you will find the following code at the beginning of the file:

var (    ErrInvalid    = errors.New("invalid argument")    ErrPermission = errors.New("permission denied")    ErrExist      = errors.New("file already exists")    ErrNotExist   = errors.New("file does not exist"))

These are the OS package export error code variables, because it is exported, we call the OS package function return after the error code can be used to determine the direct use of equal to the decision, such as:

err := os.XXXif err == os.ErrInvalid {    //handle invalid}... ...

You can also use the switch case:

switch err := os.XXX {    case ErrInvalid:        //handle invalid    case ErrPermission:        //handle no permission    ... ...}... ...

(As for the error type variable with the OS.) Errinvalid's comparability can be referred to go specs.

Generally for the design and implementation of the library, the design of the library should consider the export of which error variables.

3. Define your own error interface implementation type

If you want to provide additional error context, we can define our own type of implementation error interface, and if these types are exported, we can use type assertion or type switch to determine the type of error code returned and to handle it accordingly.

such as $goroot/src/net/net.go:

type OpError struct {    Op string    Net string    Source Addr    Addr Addr    Err error}func (e *OpError) Error() string {    if e == nil {        return "
  
   
    
   "    }    s := e.Op    if e.Net != "" {        s += " " + e.Net    }    if e.Source != nil {        s += " " + e.Source.String()    }    if e.Addr != nil {        if e.Source != nil {            s += "->"        } else {            s += " "        }        s += e.Addr.String()    }    s += ": " + e.Err.Error()    return s}
  
   

Net. Operror provides a rich error Context, not only that it also implements a method other than error, such as: Timeout (implementing Net.timeout Interface) and temporary (implement Net.temporary interface). This way we can convert the error to *net with type assertion or type switch when we are dealing with error. Operror, and calls to the timeout or temporary method to implement some special judgments.

err := net.XXXif oe, ok := err.(*OpError); ok {    if oe.Timeout() {        //handle timeout...    }}

V. Pit (s)

Each programming language has its own exclusive pit (s), Go is a noble, but after all, young, pit also a lot, in error processing this piece can also list a few.

1, Go faq:why is my nil error value not equal to nil?

type MyError stringfunc (e *MyError) Error() string {    return string(*e)}var ErrBad = MyError("ErrBad")func bad() bool {    return false}func returnsError() error {    var p *MyError = nil    if bad() {        p = &ErrBad    }    return p // Will always return a non-nil error.}func main() {    err := returnsError()    if err != nil {        fmt.Println("return non-nil error")        return    }    fmt.Println("return nil")}

The above output is "return Non-nil error", meaning that after Returnserror returns, err! = nil. Err is a interface type variable whose underlying consists of two parts: a type and a value. Err is nil only if both of these parts are nil. But Returnserror returns a value of nil, but a variable of type *myerror is assigned to err, so that err is not nil. Workaround:

func returnsError() error {    var p *MyError = nil    if bad() {        p = &ErrBad    }    return nil}

2, switch err. Match order of (type)

Imagine the output from the following code:

type MyError stringfunc (e MyError) Error() string {    return string(e)}func Foo() error {    return MyError("foo error")}func main() {    err := Foo()    switch e := err.(type) {    default:        fmt.Println("default")    case error:        fmt.Println("found an error:", e)    case MyError:        fmt.Println("found MyError:", e)    }    return}

You may think that the output: "Found Myerror:foo error", but the actual output is: "Found an error:foo error", that is, E first match to error! If we change the order:

... ...func main() {    err := Foo()    switch e := err.(type) {    default:        fmt.Println("default")    case MyError:        fmt.Println("found MyError:", e)    case error:        fmt.Println("found an error:", e)    }    return}

This time the output turns into: "Found Myerror:foo error".

You might think that this is not all the wrong pit, it's about the order of the switch case, but it's undeniable that some people write code like this, and once that's written, the pit will step in. For this reason, the "generic" type of error is left behind or removed from the case where the error type is determined by the switch cases.

Vi. third-party libraries

If you feel that go built-in error mechanism does not meet your needs well, the spirit of "do not reinvent the wheel", we recommend using some third-party libraries to meet, such as: Juju/errors. This is not to be discussed here.

Bigwhite. All rights reserved.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

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.