pseudoyu

pseudoyu

Blockchain | Programming | Photography | Boyi
github
twitter
telegram
mastodon
bilibili
jike

Go Error Handling Summary and Practice

Preface#

Recently, I have been reviewing and summarizing the Geek Time's Go Advanced Training Camp by Teacher Mao Jian. This is a course that is more focused on engineering and theoretical aspects, covering a wide range of knowledge points. Therefore, I decided to start a series to record and facilitate my own summary and reference. This is the first article in the series, "Go Error Handling".

Go Error Handling Mechanism#

Built-in Errors in Go#

In Go language, error is just an ordinary interface that represents a value.

// http://golang.org/pkg/builtin/#error
// Definition of the error interface

type error interface {
    Error() string
}

// http://golang.org/pkg/errors/error.go
// Building error objects in errors package

type errorString struct {
    s string
}

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

There are many custom error types in the standard library, such as Error: EOF, and errors.New() returns a pointer to an internal errorString object.

Error vs. Exception#

Unlike languages like Java and C++, Go does not introduce exceptions to handle errors. Instead, it adopts the approach of returning multiple values, allowing error interface objects to be passed to callers for handling.

func handle() (int, error) {
    return 1, nil
}

func main() {
    i, err := handle()
    if err != nil {
        return
    }
    // Other processing logic
}

It should be noted that Go has the mechanism of panic, which can be combined with recovery to achieve similar effects to try...catch... in other languages. However, Go's panic is not equivalent to exceptions. Exceptions are generally handled by callers, while Go's panic is for truly exceptional situations (such as index out of range, stack overflow, unrecoverable environmental issues), which means that the code cannot continue to run and cannot assume that the caller will resolve the panic.

Go's approach of using multiple return values to support error handling gives developers great flexibility. It has the following advantages:

  • Simplicity
  • Plan for failure, not success
  • No hidden control flow
  • Complete control over error handling by developers
  • Error is a value, so it is highly flexible to handle

Best Practices for Go Error Handling#

panic#

Panic is only used in truly exceptional situations, such as:

  • When a strongly dependent service fails during program startup, panic and exit
  • When obvious configuration mismatches are found during program startup, panic and exit (defensive programming)
  • At the program entry point, for example, gin middleware needs to use recovery to prevent the program from exiting due to panic

Because panic causes the program to exit directly, and if recovery is used for handling, it has poor performance and is uncontrollable. Therefore, in other cases, as long as it is not an unrecoverable program error, panic should not be used directly. Instead, errors should be returned to be handled by developers.

error#

In general, we use github.com/pkg/errors to handle application errors, but it should be noted that we generally do not use it in public libraries.

When judging errors through multiple return values, error should be the last return value of the function, and when error is not nil, other return values should be in an unusable state and should not be processed separately. When handling errors, the error should be checked first, and when if err != nil, the error should be returned in a timely manner to avoid excessive code nesting.


// Incorrect example

func f() error {
    ans, err := someFunc()
    if err == nil {
        // Other logic
    }

    return err
}

// Correct example

func f() error {
    ans, err := someFunc()
    if err != nil {
        return err
    }

    // Other logic
    return nil
}

When an error occurs in the program, errors.New or errors.Errorf is generally used to return the error value.

func someFunc() error {
    res := anotherFunc()
    if res != true {
        errors.Errorf("Result error, tried %d times", count)
    }
    // Other logic
    return nil
}

If there is a problem with calling another function, it should be returned directly. If additional information needs to be carried, errors.WithMessage should be used.

func someFunc() error {
    res, err := anotherFunc()
    if err != nil {
        return errors.WithMessage(err, "other information")
    }
}

If an error is obtained when calling other libraries (standard libraries, enterprise common libraries, open source third-party libraries, etc.), please use errors.Wrap to add stack information. It should only be used when the error first occurs, and it is generally not used in basic libraries and third-party libraries that are heavily referenced to avoid duplicate stack information.

func f() error {
    err := json.Unmashal(&a, data)
    if err != nil {
        return errors.Wrap(err, "other information")
    }

    // Other logic
    return nil
}

When it is necessary to judge errors, errors.Is should be used for comparison.

func f() error {
    err := A()
    if errors.Is(err, io.EOF){
    	return nil
    }

    // Other logic
    return nil
}

When judging error types, errors.As should be used for assignment.

func f() error {
    err := A()

    var errA errorA
    if errors.As(err, &errA){
    	// ...
    }

    // Other logic
    return nil
}

For business errors (such as input errors, etc.), it is best to establish your own error dictionary in a unified place, which should include error codes and can be printed as independent fields in logs, and clear documentation should be provided.

We often use logs to assist us in error handling. Errors that do not need to be returned or ignored must be logged. However, it is forbidden to log errors in every error-prone place. If the same place keeps reporting errors, it is best to print the error details once and print the occurrence count.

Summary#

The above is a summary of Go error handling and best practices. In the future, I will also summarize error types, error wrapping, and common pitfalls encountered in usage.

References#

  1. Best Practices for Go Error Handling
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.