3 ms·
This blog is worth a read even if you don't care about Upspin or Go. Some details are certainly specific to Upspin (and they're marked as such), but there are
by heavenlyhash 9y ago
This blog is worth a read even if you don't care about Upspin or Go. Some details are certainly specific to Upspin (and they're marked as such), but there are also patterns worth thinking about for programmers of any background.
My favorite highlights:
> it is critical that communications [between Upspin servers] preserve the structure of errors
YES. This is something that's wildly underconsidered in most projects and indeed most programming languages. Errors need to be serializable... and deserializable, losslessly. Having errors which can serialize round-trip is a superpower. It unlocks the ability to have -- well, the blog gets to it later:
> an operational trace, showing the path through the elements of the system, rather than as an execution trace, showing the path through the code. The distinction is vital.
YESSS. From personal anecdote: I've been writing a collection of software recently which is broken up into several different executables. This is necessary and good in the system's design, because they have different failure domains (it's nice to send SIGINT to only one of them), and they also need to do some linux voodoo that's process-level, yada yada. The salient detail is that in this system, there are typically three processes deep in a tree for any user action. That means I need to have errors from the third level down be reported clearly... across process boundaries.
An operational trace is the correct model for this kind of system. Execution traces from any single process are relatively misguided.
> The Kind field classifies the error as one of a set of standard conditions
This is something I've seen emerge from many programmers lately, independently (and from a variety of languages)! There must be something good at the bottom of this idea if it's so emergent.
The "Kind" idea is particularly useful and cross-cutting. They're serializable -- trivially, and non-recursively, because they're a simple primitive type. And, per the guidelines of what would be useful in a polyglot world, they're also virtuous in that they're pretty much an enum, which can be represented in any language, with or without typesystem-level help.
(I also have a library which builds on this concept; more about that later.)
---
Now, some things I disagree with:
Right after describing the importance of serializable errors, Pike goes on to mention:
> we made Upspin's RPCs aware of these error types
This is a colossal mistake. Or rather, it's a perfectly reasonable thing to do when developing a single project; but it severely limits the reusability of the technique, and also limits what we can learn from it.
I'm a polyglot. I'd like to see a community -- with representatives from all languages -- focus on a simple definition of errors which can be reliably parsed and round-tripped to the wire and back in any language. For gophers, a common example should be Go and Javascript: I would like my web front-end to understand my errors completely!
I think this is eminently doable. Imagine a world where we could have the execution flow trace bounce between a whole series of microservices, losslessly! It's worth noting, however, that it would require an investment in keeping things simple; and in particular, controversially, not leaning on the language's type system too much, since it will not translate well if your program would like to interact with programs in other languages. Simplicity would be key here. Maps, arrays, and strings.
---
Lastly, I said earlier I have a library. It's true. (I have two libraries, actually; one in which I tried to use the Go type system more heavily, and included stack traces -- both of which I've come to regard as Mistakes over time and practice; I'll not speak of that one.)
Gophers might be interested in this, but also I'd love commentary from folk of other language backgrounds:
https://godoc.org/github.com/polydawn/go-errcat https://godoc.org/github.com/polydawn/go-errcat
This library is an experiment, but using it in several projects has been very pleasing so far. It has many of the same ideas as in this blog, particularly "categories" (which are a clear equivalent of "kinds" -- what's in a name?). Some ideas are taken slightly farther: namely, there is one schema for serializing these errors, and it very clearly coerces things into string types early in the program: the intention of this is to make sure your program logic has exactly as much information available to it as another program will if deserializing this error later; any more information being available in the original process would be a form of moral hazard, tempting you to write logic which would be impossible to replicate in external process.
The main experiment I have in mind with this library is what's the appropriate scope for a kind/category? Does it map cleanly to packages? Whole programs? Something else entirely?