Summary and practice of Go error handling

Posted by syth04 on Fri, 19 Nov 2021 16:16:35 +0100

preface

Recently, I reviewed and summarized the Go advanced training camp of geek time teacher Mao Jian. This is a course that is more inclined to engineering and principle. It covers a lot of knowledge points. Therefore, I decided to open a series to record, which is also convenient for me to summarize and review. This is the first in a series, "Go error handling.".

Go error handling mechanism

Go built in errors

error in Go language is a common interface that represents a value

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

type error interface {
    Error() string
}

// http://golang.org/pkg/errors/error.go
// errors build the error object

type errorString struct {
    s string
}

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

There are a large number of custom errors in the basic library, such as Error: EOF, and errors.New() returns the pointer of the internal errorString object.

Error and Exception

Different from Java, C + + and other languages, the logic of Go handling exceptions does not introduce exception, but adopts multi parameter return. Therefore, the error interface object can be brought into the function and handed over to the caller for processing.

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

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

It should be noted that there is a panic mechanism in Go, which can be combined with recovery to achieve an effect similar to try...exception... But the panic in Go is not equal to exception. Exception is generally handled by the caller, while Go panic is for real exceptions (such as index out of bounds, stack overflow, unrecoverable environmental problems, etc.), This means that the code cannot continue to run, and it cannot be assumed that the caller will solve the panic.

Go's multiple return values to support the caller's error handling gives developers great flexibility and has the following advantages

  • simple
  • Plan for failure, not success
  • There is no hidden control flow
  • It is entirely up to the developer to control error
  • error is a value, so it has great flexibility to handle it

Go error handling best practices

panic

panic is only used in truly abnormal situations, such as

  • When the program starts, panic exits if a strongly dependent service fails
  • When the program starts, if it is found that the configuration obviously does not meet the requirements, panic can exit (Defense programming)
  • At the program entrance, for example, the gin middleware needs to use recovery to prevent the panic program from exiting

Because panic will cause the program to exit directly, and if recovery is used for processing, the performance is poor and uncontrollable. Therefore, in other cases, as long as it is not an unrecoverable program error, panic should not directly return error to the developer.

error

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

When judging the error through multiple return values, error should be the last return value of the function. When error is not nil, other return values should be unavailable and should not be processed additionally. When handling the error, you should also judge the error first. When if error= Nil returns errors in time to avoid too much code nesting.

// Error 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 a program, errors.New or errors.Errorf are generally used to return the error value

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

If there is a problem calling other functions, it should be returned directly. If additional information needs to be carried, use errors.WithMessage.

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

If you call other libraries (standard library, enterprise public library, open source third-party library, etc.) to get errors, please use errors.Wrap to add stack information. It only needs to be used when the error occurs for the first time, and it is generally not used when writing basic libraries and heavily referenced third-party libraries to avoid stack information duplication.

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

    // Other logic
    return nil
}

When you need to judge errors, you need to use errors.Is for comparison

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

    // Other logic
    return nil
}

When judging the error type, use errors.As 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), it is best to establish your own error dictionary in a unified place, which should contain error codes and can be printed as independent fields in the log. Clear documents are also required.

We often use logs to assist us in error handling. We must output logs for ignored errors that do not need to be returned, but it is forbidden to log every error. If you keep reporting errors in the same place, it's best to print the details of the error and the number of occurrences.

summary

The above is a summary of Go error handling and best practices. Later, we will also summarize the error types, wrong packaging and common pits encountered in use.

reference material

  1. Go error handling best practices

Topics: Go