Sunday, October 25, 2015

Handling Errors in Go


Using Golang best practices for handling errors, we handle the error ASAP if it's not nil.

This avoids nested blocks of logic and is generally a good idea in any language.

Frequently, we want to return an error message in the form of a formatted string.

First Try

This technique requires both fmt and error packages to be imported.


   err = db.Select(&customer, "SELECT * FROM customer WHERE id = ?", 999)
   if err != nil {
      err = errors.New(fmt.Sprintf("Unable to process customer: %v [Error: %+v]", id, err))
      return
   }

Better - Use fmt.Errorf

  • Fewer imports (only requires fmt)
  • Fewer function calls
  • Less code


   err = db.Select(&customer, "SELECT * FROM customer WHERE id = ?", 999)
   if err != nil {
      err = fmt.Errorf("Unable to process customer: %v [Error: %+v]", id, err)
      return
   }

Generally speaking...

  • the fewer packages you include, the less there is to break now and in the future
  • the fewer function calls you make, the faster your code is going to run
  • the less code you have, the less code you have to review, compile and maintain

Okay, But Can Do Even Better?

Granted, we've simplified our code, but if we use the New function from the errors package to create package level, exported defined error variables we can return those to a caller that can compare them.

Define Errors

Define errors in customer package:


ErrCustomerNotFound := errors.New("customer: id not found")
ErrTimeout := errors.New("customer: timeout")

Compare Error


response, err := processCustomer()
if err != nil {
    switch err {
    case customer.ErrCustomerNotFound:
        // Handle customer not found error.
        return
    case customer.ErrTimeout:
        // Handle timeout error.
        return
    default:
        // General error handler
        return
    }
}

Return Error Interface Type

It is idiomatic in Go to use the error interface type as the return type for any error that is going to be returned from a function or method.

This interface is used by all the functions and methods in the standard library that return errors.

For example, here is the declaration for the Get method from the http package:


http://golang.org/pkg/net/http/#Client.Get

func (c *Client) Get(url string) (resp *Response, err error)


Since we have defined our err return argument in our function definition, we can use a simple return command and our err value will be returned through the error interface type definition.

Even Better

Go's design encourages us to explicitly check for errors where they occur; Contrast this with other languages that encourage throwing exceptions and sometimes catching them.

However, this can lead to a lot of error handling code.

How can we reduce our code and add features, like adding an error code, that can be leveraged to support I18N requirements?

If we have one service handler which is the only place that handles processCustomer calls, we can return a service-specific error object.


type svcError struct {
    Code    int
    Error   error
}


The code can be used to lookup the appropriate, localized message and the original error can be passed along to the handler.


func processCustomer(req *svc.Request, resp svc.Response) *svcError {    
    if custID, err := req.Get("id"); err != nil {
        return &svcError{CustomerIdNotFound, err}
    }
    return nil
}


It helps to define error code constants.

const CustomerIdNotFound = 10001

Improvements


  • Allow svcError to take a slice of parameters that can be used to construct a more complicated error message
  • Include a StackTrace
  • Handle panics in svcError and report a more user friendly message to the consumer




References


This work is licensed under the Creative Commons Attribution 3.0 Unported License.

1 comment:

  1. An interface is being used to handle error values, a concrete type needs to be declared that implements the interface. The standard library has declared and implemented this concrete type for us in the form of a struct called errorString. In this post, we will explore the implementation and use of the error interface and errorString struct from the standard library.

    ReplyDelete