simon

simon

github

Error Handling in Go

Error Handling in Go#

Created: December 7, 2021 6:24 PM
Tags: Golang
Published: December 29, 2021

Problem#

One of the most annoying issues in the Go language is error handling.

Every function needs to return an error, and each return requires a check, resulting in a significant portion of code dedicated to handling errors. Moreover, the code for handling errors is often redundant and repetitive. As someone who has been influenced by the simplicity philosophy of Python, this is a great source of pain for me.

Fortunately, this problem can be optimized. Of course, better readability may come at the cost of some performance, but considering that Go already has excellent performance and readability is a significant concern, using this approach to reduce error handling code in complex logic areas can be beneficial.

Solution#

Let's demonstrate with an example:

// Let's start with some code

// Function that handles string comparison
func A(a, b string) (string, error) {
    if a != b {
        return "", errors.NewErr("info")
    }
    return a, nil
}

// Function that handles integer comparison
func B(a, b int) (int, error) {
    if a != b {
        return 0, errors.NewErr("info")
    }
    return a, nil
}

// Main function
func Main(a, b string, c, d int) error {
    // In the conventional approach, error handling is required for each function call
    res, err := A(a, b)
    if err != nil {
        return err
    }
    res, err = B(c, d)
    if err != nil {
        return err
    }
    //  100 function calls omitted here
    return nil
}

From the above code, we can see that for each function call in the main function, error handling is required. This leads to the logic of the function call being overshadowed by error handling.

Our solution is to modify the called functions to handle errors as well, moving the error handling from the main function to each individual function. This way, the more a function is reused, the more code is saved. Additionally, it improves readability.

Let's demonstrate with an example:

func A(a, b string, err error) (string, error) {
    // Added an 'err' parameter and the following logic
    // Accepts an 'error' parameter and returns if it is not nil
    if err != nil {
        return "", err
    }
    if a != b {
        return "", errors.NewErr("info")
    }
    return a, nil
}

func B(a, b int) (int, error) {
    if err != nil {
        return 0, nil
    }
    if a != b {
        return 0, errors.NewErr("info")
    }
    return a, nil
}

func Main(a, b string, c, d int) error {
    res, err := A(a, b, nil)
    res, err = B(c, d, err)
    //  100 function calls omitted here
    //  As you can see, regardless of the number of function calls, error handling is only written once
    if err != nil {
        return err
    }
    return nil
}

Reflection#

Can this approach be used universally to solve our error handling problem?

I don't think this is a universal solution. It has a few drawbacks:

  1. Although subsequent functions won't execute if one of them returns an error, there is still function call and parameter passing overhead, resulting in some performance loss. This approach is not suitable for parts where extreme performance is desired.
  2. This approach may increase the difficulty of debugging because error information is returned after executing all the logic, making it challenging to pinpoint the exact location of the error. However, there are solutions to this problem, such as adding function stack information to the error message, but it adds complexity.

Conclusion#

This error handling approach can significantly improve code readability, but it may have a slight impact on performance and add some debugging complexity. However, for complex code, I believe the trade-off of sacrificing a bit of performance for improved readability is worth it.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.