errgo

module
v2.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 23, 2018 License: BSD-3-Clause

README

Package errgo provides some primitives for error creation and handling.

It provides primitives for wrapping and annotating errors without exposing implementation details unnecessarily.

Error dependencies as code fragility

If we import a package, how much of its exposed API are we entitled to use? Most packages do not explicitly document the set of possible public errors that can be returned from their functions, so if we see that a package returns a particular type or value, can we be blamed for writing code that relies on it?

I cannot be the only one that has run a function that's returning an error I want to handle, looked at the error's type and added a test against that type. That type isn't necessarily in the package that I'm calling, but may be several layers deeper than that. Once that condition is there in the my code, it may be broken if any of the layers below it fails to preserve or generate the errors values my code is now expecting.

For example, say I'm developing text-editor integration for my favourite cloud app. When using it to parse the app's configuration data, I find that it returns json.SyntaxError when there's a configuration error. I write code that, when it finds json.SyntaxError, jumps the editor cursor to the place where the error was found (by looking at SyntaxError.Offset).

Some time later, someone comes along and decides that encoding/json is too slow, and replaces it with xxx/fastjson. Since encoding/json isn't in the public API surface area, this doesn't seem like a breaking change, so the change lands, but in actual fact my code is now broken.

When code bases get sufficiently large, this makes refactoring code considerably more error-prone, because it is not always clear whether it's OK to change some code or not. The code is fragile.

Wrapping breaks error dependencies

A common idiom is to return an error "annotated" with some extra text to give it more context. If we do that, then we protect ourselves from the subtle breakage illustrated above, because callers cannot depend on the underlying error any more. If I want to add my feature, I need to notify upstream and tell them that I'd like to be able to find where the config file syntax error is, and they'll need to land a change that makes it explicitly available in the API. This is more work, but it means that the error type is now an explicit part of the API and hence much less likely to be changed in a backwardly incompatible way.

The problem with fmt.Errorf is that it hides even error causes that we want to expose. It's common to have a few well-known error return values

I find a package that can talk to the app, and I find that when

My code uses the tool's config p

calling a package that uses (internally) encoding/json to parse its configuration format. I'm building an

Even if we're only importing a package for internal use, if we're returning its errors, we're exposing ourselves to that kind of

example, changing to use a different package that's only used internally might end up breaking some other code that happens to be if any of those layers changes to use a different lower layer, or

Fragility 1: "That code doesn't return that kind of error any more!"

Fragility 2: "I thought that error meant this not that as well!"

Some code makes a conditional test on an error. It may compare the error for equality with a value defined in another package (io.EOF), or call a function (os.IsNotExist) or do a dynamic type comparison. There is a dependency made between the code doing the comparison and the code that defines the value, function or type. This dependency is not visible directly in any call graph but bugs and API incompatibilities can easily arise when dependencies change.

Since errors can propagate back through many layers of stack and layers of abstraction (and are commonly returned exactly as received), a caller might be testing an error returned from a package that an intermediate package considers an implementation details.

Even though error values and types are not generally well documented (or perhaps because they're not), these dependencies arise commonly because, on finding some error that we want to handle, we will run the program or look at the code, then write a test against the type we see.

This

and try to find something about the error that can be used to distinguish the problem we're seeing, and often that will involve a test against an error␣ value

I believe that most programmers will try to avoid string comparisons (known to be fragile), but testing against a known type seems more robust.

diagnoses the problem what we're seeing.

some kind of database package, and as part of that implementation, it uses an encoding package.

initially uses a file based store.

It is currently very common to return exactly the error that was

when encountering an error

When code tests an error against a particular type or value to decide what to do

The is informed by the view that

hiding is important

It aims to decrease implicit inter-package dependency

Directories

Path Synopsis
The errors package provides a way to create and diagnose errors.
The errors package provides a way to create and diagnose errors.
fmt
errors
Package errors is the same as gopkg.in/errgo.v2/errors except that it adds convenience functions that use the fmt package to format error messages.
Package errors is the same as gopkg.in/errgo.v2/errors except that it adds convenience functions that use the fmt package to format error messages.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL