SoFunction
Updated on 2025-03-04

Summary of error handling methods in Go language

Go's error has two very important features:

  • error is an ordinary value without additional overhead when processing
  • The error has good scalability and can customize errors according to different scenarios.

After Go1.13, some functions were provided in the errors package to make error handling and tracking more convenient.

This article will discuss the common usage of errors in Go in combination with the functions in errors.

The errors package mentioned here refers to the native errors package in Go.

1. Native error

In Go's error handling, the following code accounts for the vast majority:

if err != nil {
   return err
}

When meeting business needs, this error handling is actually the most recommended way. This direct transmissive method makes the coupling between codes less. In many cases, if you don't care about the specific information in the error, you can use this method.

2. Define error in advance

Native errors are not very convenient to use in some cases. For example, I need to obtain specific error information. If I still use the error in the above method, the following code may appear:

if err != nil && () == "invalid param" {
}

Anyone who has written the code knows that the above code is not elegant. In addition, if the error information changes, the code logic here will be incorrect. You can define the error as a variable:

var (
    ErrInvalidParam = ("invalid param")
)

Then the above code can become like this:

if err != nil && err == ErrInvalidParam {
}

If there are many errors that need to be processed at one time, you can also use switch to handle them:

if err != nil {
    switch err {
    case ErrInvalidParam:
        return
    case ErrNetWork:
        return
    case ErrFileNotExist:
        return
    default:
        return
    }
}

But this method is not perfect, because error may be wrapped during the process of passing it to carry more stack information, such as the following:

if err != nil {
    // When packaging errors, use %w to format errors here    return ("add error info: %+v, origin error: %w", "other info", err)
}

Assuming that the error wrapped above is ErrInvalidParam, then the following code cannot be used to judge the error in the call:

if err != nil && err == ErrInvalidParam {
}

To solve this problem, the function can determine whether there is an expected error in the wrapped error:

if (err, ErrInvalidParam) {
}

Try to use , instead of comparison of error .

3. Use a custom error type

The above error usage method still cannot meet the requirements in some cases. If the above invalid parameter error, if the business party wants to know which parameter is invalid, the direct definition error cannot meet the requirements. An error is essentially an interface, that is, as long as the Error method is implemented, it is an error type:

type error interface {
    Error() string
}

Then you can customize an error type:

type ErrInvalidParam struct {
    ParamName  string
    ParamValue string
}
func (e *ErrInvalidParam) Error() string {
    return ("invalid param: %+v, value: %+v", , )
}

Then you can use the type assertion mechanism or type selection mechanism to deal with different types of errors:

e, ok := err.(*ErrInvalidParam)
if ok && e != nil {
}

It can also be used in switch:

if err != nil {
    switch err.(type) {
    case *ErrInvalidParam:
        return
    default:
        return
    }
}

Here, error will also have the problem of being wrapped, and it can be used to solve this problem. You can determine whether there is a certain error type in the packaged error:

var e *ErrInvalidParam
if (err, &e) {
}

4. More flexible error type

The above method can already solve the error processing in most scenarios, but in some complex situations, more information may be needed from the error and also includes certain logical processing.

In Go's net package, there is an interface like this:

type Error interface {
    error
    Timeout() bool  
    Temporary() bool
}

In this interface, there are two methods. These two methods will process this error type and determine whether it is a timeout error or a temporary error. Implementing the error of this interface requires implementing these two methods and implementing specific judgment logic.

When processing specific errors, the corresponding method will be called to judge:

if ne, ok := e.(); ok && () { 
     // Handle temporary errors}
if ne, ok := e.(); ok && () { 
     // Handle timeout errors}

Relatively speaking, this type of error will be used relatively few. Generally speaking, try not to use such a complicated processing method.

5. Other abilities in errors

In the errors package, in addition to the two useful functions mentioned above, there is also a more practical function, which can parse the original error from the wrapped error.

You can use : to wrap the error, and you need to use %w format:

return ("add error info: %+v, origin error: %w", "other info", err)

During subsequent error processing, you can call the function to obtain the error before being wrapped:

err = (err)
("origin error: %+v\n", err)
package main
import (
	"errors"
	"fmt"
)
func main(){
	err1 := ("zero")
	("origin error: %+v\n", err1)
	err2 := ("add error info: %+v, origin error: %w", "other info", err1)
	("origin error: %+v\n", err2)
	err3 := (err2)
	("origin error: %+v\n", err3)
}

Program output

origin error: zero
origin error: add error info: other info, origin error: zero
origin error: zero

6. Pay attention

If you need to use goroutine, you should use a unified Go function to create it. This function will recover to avoid the main process exiting due to wild goroutine panic.

func Go(f func()){
    go func(){
        defer func(){
            if err := recover(); err != nil {
                ("panic: %+v", err)
            }
        }()
        f()
    }()
}

When an error occurs in the application, use or return an error:

("User balance is insufficient, uid: %d, money: %d", uid, money)

If there is an error in calling other functions of the application, please return directly. If you need to carry information, please use .

// /pkg/errors
(err, "Other Additional Information")

If an error is obtained by calling other libraries (standard libraries, enterprise public libraries, open source third-party libraries, etc.), please use Add stack information.

Remember, don't use it everywhere. Just do it when the error first occurs.

Determine whether the original errors of other libraries need to be swallowed. For example, you can swallow the database-related errors in the repository layer and return the business error code to avoid the need to modify the upper layer code when we divide the microservices or replace the ORM library in the future.

Note that we generally do not use the basic library when writing a large number of third-party libraries introduced to avoid duplication of stack information.

func f() error {
    err := (&a, data)
    if err != nil {
        return (err, "Other Additional Information")
    }
    // Other logic    return nil
}

It is prohibited to log every error where it occurs. You only need to use %+v to print uniformly at the beginning of the process, such as the middleware of the http/rpc service.

Error judgment use for comparison:

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

For error type judgment, use to assign values:

func f() error {
    err := A()
    var errA errorA
    if (err, &errA){
    }
    // Other logic    return nil
}

The above is the detailed content of the summary of the error handling method in Go. For more information about Go error handling, please pay attention to my other related articles!