2023-12-28

Learning Error Handling From Golang.

Four Patterns of Error Handling

    1. Delegate to upstream.
    1. Log(Provide feedback to User).
    1. Retry
    1. Cease Continuation and Trigger Panic/Exit

Principles of Error Handling

Avoid delegating Error Handling to upstream as much as possible

The deeper the hierarchy, the more complex and difficult it becomes to follow the process. It's good that upstream just do "Logging Error" about Error Handling. Handling other than logging should be done close to the process calling.

Avoid defining Custom Error as much as possible.

Custom Error involves delegating error handling to upstream. Let's handle errors as close to the process as possible.

If your error message is good, you don't need stacktrace.

Stacktrace shows its value only when error message is not useful.

Wrap & Unwrap

Go 1.13 supports Wrap/Unwrap.

This is a common case before Go 1.13.

// Error Containing Another Error.
type QueryError struct {
    Query string
    Err   error
}

func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() }

The pattern of one error containing another error is pervasive(common). So, Go 1.13 supports for it.

Wrap

You can wrap error by %w.

if err != nil {
  return fmt.Errorf("QueryError %w", err);
}

Unwrap

errors.Isorerrors.As` recursively checks the wrapped error (It calls Unwrap() internally).

if errors.Is(err, ErrPermission) {
  // Some ErrorHandling 
}

Whether to Wrap or Not?

It depends on the context. Do not wrap an errror when doing so would expose implementation details. You should care about Abstraction Violation.

Ex1. Parser

Imagine Parser which reads a complex data structure. If an error occurs, we wish to report the line and column number at whitch it occurred. It makes sense to expose the error produced by it.

Ex2. Database Caller

Imagine Function which makes several calls to a database. It should not return an error which unwrap s to the result of one of those calls. If the database used by the function is an implementation detail, then exposes errors is a violation of abstraction.

// BAD: Abstraction Violation.
// If you wrap sql.ErrNoRows, your caller need to know sql.ErrNoRows to handle the error.
err := repo.LookupUser
if errors.Is(err, sql.ErrNoRows)

Conclusion: Whether to wrap

  • Whether you wrap or not, the error text will be the same. User gets the same information either way.
  • The choice to wrap is about whether to give programs additional infomation so that they can make more informed decisions or to withhold that information to preserve an abstraction layer

References