Home > Software engineering >  How should we handle errors when return it in go? [closed]
How should we handle errors when return it in go? [closed]

Time:09-17

Which is better?

1
err := MakeFood()
if err != nil{
    return err
}
2
err := MakeFood()
if err != nil{
    logs.Errorf("failed to make food, error=%v",err)
    return err
}
3
err := MakeFood()
if err != nil{
    return fmt.Errorf("failed to make food, error=%w",err)
}
4
var ErrMakeFood = errors.New("failed to make food")

err := MakeFood()
if err != nil{
    return ErrMakeFood // we discard err
}

In my practise, return fmt.Errorf("xxx, error=%w",err) is my favorite, which creates a cascaded error string when error happens and return.
But it seems that, in go builtin src code, return err is normal and tidy.
And sometimes we are suggested to use static error declarations(the example 4 I gave). This is the lint rule of a golang-lint named goerr113.

CodePudding user response:

No one is better, they are all different and can be appropriate for different cases.

The 1st one,

err := MakeFood()
if err != nil{
    return err
}

is OK when MakeFood is known to provide its own context for all errors it returns (or wraps and returns — "bubbles up"); for instance, any error it ever returns is wrapped in the "failed to make food: " context.

It also fits for other cases when there's no need to provide any additional context.

The second and third are appropriate in the context of a bigger function which performs some conceptually single (atomic) task which consists from multiple steps (and making food is one of such steps); in this case it's customary to wrap errors reported by each of the steps in the same context—leading to context "chaining" when each call in the call chain adds its own context. For instance, if our bigger function processes a food delivery order, it could look like this:

func ProcessFoodDeliveryOrder() error {
  meal, err := MakeFood()
  if err != nil {
    return fmt.Errorf("failed to process order: %w", err)
  }
  err := Deliver(meal, address)
  if err != nil {
    return fmt.Errorf("failed to process order: %w", err)
  }
  return nil
}

The question of whether one should use the newer Go facility of actually nesting errors by using the %w formatting verb—as opposing to merely providing textual (message) context—is an open one: not all errors need to be wrapped (and then later tested by errors.Is or handled by errors.As); there's no need to use that facility just because you know it exists and looks cool: it has its runtime cost.

The error message is better formatted using plain : %s combo—like with fmt.Errorf("new context: %s", err) because chaning the errors this way produces a trivially understandable and easily read "action: reason" text. BTW it's recommended in The Book.

The latter approach is called "a sentinel error". It's okay to use if the users of your package will actually use such error variable—directly or by testing it using errors.Is.
But also consider that it may be better to assert the behaviour of the errors, not comparing them to a sentinel variable. Consider reading this and look at net.Error.

  • Related