Hello everyone, I am fried fish.

A year and a half ago, I shared the article "Sneak Peek , The Struggling Road of Go2 Error ", which covers the problem of Go1 error handling, the enhancement of Go1.13, and the detailed explanation of Go2's new error handling proposal. How many of you remember the "bright" future of Go2's new bug proposal?

Go2's new proposal also received a lot of criticism at the time. @Liam Breck criticized it in "Golang, how dare you handle my checks!", let's learn together!

Review grammar

In August 2018, the official Go 2 Draft Designs were officially released, which contained preliminary drafts of generics and error handling improvements:

Go 2 Draft Designs

Below is the key new syntax for Go2 error handling.

Error Handling

The first problem to be solved is a large number of problems if err != nil , for which a draft design of Go2 error handling is proposed.

Simple example:

 if err != nil {
    return err
}

The optimized scheme is as follows:

 func CopyFile(src, dst string) error {
    handle err {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }

    r := check os.Open(src)
    defer r.Close()

    w := check os.Create(dst)
    handle err {
        w.Close()
        os.Remove(dst) // (only if a check fails)
    }

    check io.Copy(w, r)
    check w.Close()
    return nil
}

Main function:

 func main() {
    handle err {
        log.Fatal(err)
    }

    hex := check ioutil.ReadAll(os.Stdin)
    data := check parseHexdump(string(hex))
    os.Stdout.Write(data)
}

The proposal introduces two new syntax forms, the first is the check keyword, which selects an expression check f(x, y, z) or check err , which will identify the is an explicit error check.

Secondly, the return handle keyword is introduced, which is used to define the flow of error handlers, tossing them step by step, and so on.

Error Printing

The second problem to be solved is the problem of Error Values and Error Inspection, which leads to the problem of Error Printing, which can also be considered as the inconvenience of wrong formatting.

The official proposed a draft design of Error Values and Error Printing for this.

A simple example is as follows:

 if err != nil {
    return fmt.Errorf("write users database: %v", err)
}

The optimized scheme is as follows:

 package errors

type Wrapper interface {
    Unwrap() error
}

func Is(err, target error) bool
func As(type E)(err error) (e E, ok bool)

This proposal adds the concept of wrapping error of error chain, and also adds the methods of errors.Is and errors.As , which are consistent with the improvement of Go1.13 mentioned above, and will not be repeated here.

Go1.13 doesn't implement %+v the requirement to output call stack (no call stack, troubleshooting will be very troublesome), because this will break Go1 compatibility and cause some performance problems, it will probably be added in Go2.

Criticism of the proposal

The goal is vague

In the proposal and draft of Go2's new error handling, @Liam Breck thinks it doesn't discuss the underlying requirements. There is only a short target section, with the following four points:

  • Small footprint error checking.
  • Developer friendly error handling.
  • Explicitly check and handle errors.
  • Compatibility with existing Go1 code is guaranteed.

There is no mention of possible future expansion, but purely to meet the current demands. This category is vague and limited in inspiring new design ideas.

Unable to unify error handling

In real applications, it is very common for a function to use two or more repeated error handling methods.

The following code:

 // 方式 1
{ debug.PrintStack(); log.Fatal(err) }

// 方式 2
{ log.Println(err) }

// 方式 3
{ if err == io.EOF { break } }

// 方式 4
{ conn.Write([]byte("oops: " + err.Error())) } // network server

In the new proposal, the check and handle functions do not provide multiple ways to handle errors. This is an obvious omission, and there is no way to unify the error handling mechanism.

In this case, check and handle are just a new way to make the original error handling mechanism more complicated.

Mixing err != nil and check

The handle function is a LIFO pattern and can only jump out of one function. That is to say, it cannot handle recoverable error content very friendly.

But in practice, it is very common for many methods to return errors. Therefore, we need to use err!= nil and check at the same time, which is very cumbersome.

The following code:

 handle err { ... }
v, err := f()
if err != nil {
   if isBad(err) {
      check err
   }
   // recovery code
}

Another if err != nil, another handle, and another check function.

Nested checks are more complicated

With the check function, nested calls to function calls that return errors will obfuscate the order of operations.

While in most cases the calling order when an error occurs should be clear, it is less clear under the check function than if err != nil.

The following code:

 check step4(check step1(), check step3(check step2())

Also nested code contributes to unreadable structures:

 f1(v1, check f2(check f3(check f4(v4), v3), check f5(v5))

Now to recap, the language is forbidden.

 f(t ? a : b) 和 f(a++)

Does it seem ironic?

if err != nil works great

Go1's error handler is too friendly, ie:

 if err != nil {
    ...
}

It defeats the goal of "improving the likelihood of developers writing error handlers" by making it easy to return errors without contextual information.

Note: Personally, this point is like black and pink... The original author is black? Of course, if err != nil is really good to get started.

complex error chain

For the example below, see how it feels...

The following code:

 func f() error {
   handle err { return ... }           // finally this
   if ... {
      handle err { ... }               // not that
      for ... {
         handle err { ... }            // nor that
         ...
      }
   }
   handle err { ... }                  // secondly this
   ...
   if ... {
      handle err { ... }               // not that
      ...
   } else {
      handle err { ... }               // firstly this
      check thisFails()                // trigger
   }
}

Obviously, this code is "elusive" and we have to parse the entire function with our eyes to understand the flow and order of the entire error handling.

Will increase our cognitive load on the program.

Summarize

By reviewing the design draft of Go2 error handling, we learned the usage and ideas of the check and handle functions. In response to the new grammar, new problems that may occur have been "criticized".

In general, the new syntax will increase the complexity and readability of the existing code on the disadvantage, can lead to all kinds of strange nesting, and will repeat with if err != nil, becoming a kind of New way of handling (one more).

Wouldn't it be as pure as if err != nil?

Go Book Series

Recommended reading


煎鱼
8.4k 声望12.8k 粉丝