We carry out an audit of errors

Original author: Dave Cheney
  • Transfer

A frequently used contract for functions that return an error value of an interface type is that the caller should not know anything in advance about the state of other values ​​returned by this call without first checking the error.


In most cases, error values ​​returned by functions should be opaque to the caller. That is, a test in which the error is nil shows whether the call was successful or failed, and that’s all you need to do.



picture from here


A small number of cases, usually associated with interactions with the outside world, such as network activity, require the caller to examine the nature of the error in order to decide whether to repeat the operation.


A common requirement for package authors is to return known public type errors so that the caller can use type assert and examine them in detail. I believe that this practice leads to a number of undesirable results:


  • Open types of errors increase the "contact area" with the package API.
  • New implementations should only return the types specified in the interface declaration, even if they do not fit well.
  • The type of error, after being added to the code, cannot be changed or deprecated without breaking the compatibility, making the API fragile.

Confirm expected behavior, not error type


Do not claim that the error value is a specific type, but rather claim that the value implements a specific behavior.


This sentence is consistent with the nature of Go implicit interfaces, and is not a [subtype] of the nature of inheritance-based languages. Consider this example:


func isTimeout(err error) bool {
        type timeout interface {
                Timeout() bool
        }
        te, ok := err.(timeout)
        return ok && te.Timeout()
}

The caller can use isTimeout () to determine if the error was timed out by implementing the timeout interface, and then confirm whether the error was timed out - all without knowing anything about the type or source error values.


This method makes it possible to wrap errors, as a rule, with libraries that add explanations to the error path; provided that the wrapped error types also implement the error interfaces that they wrap.


This may seem like an insoluble problem, but in practice there are quite a few generally accepted interface methods, so Timeout () bool and Temporary () bool will cover a wide range of use cases.


Finally


Confirm expected behavior, not error type


For package authors


If your package generates errors of a temporary nature, make sure that you return error types that implement the appropriate interface methods. If you wrap output error values, make sure your wrappers support the interface (s) with which the original error value is implemented.


For package users


If you need to check for an error, use the interfaces to confirm the expected behavior, not the type of error. Do not ask package authors about open error types; ask them to align their types with common interfaces by specifying the Timeout () or Temporary () methods, as appropriate.


about the author


The author of this article, Dave Cheney , is the author of many popular packages for Go, for example, https://github.com/pkg/errors and https://github.com/davecheney/httpstat .


From translator


Although the original article is dated to the end of 2014, it seems to me that it has not lost its relevance. The described approach is found in few packages, while the rest use the usual approach for errors.


Edits to the text to increase the clarity of the material are welcome!


Also popular now: