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.