Error objects in W3C APIs
jonas at sicking.cc
Wed Feb 5 00:05:53 PST 2014
On Tue, Feb 4, 2014 at 9:49 AM, Domenic Denicola
<domenic at domenicdenicola.com> wrote:
> From: es-discuss <es-discuss-bounces at mozilla.org> on behalf of Allen Wirfs-Brock <allen at wirfs-brock.com>
>> I've designed exception handling systems before and designed and worked with complex exception hierarchies. My main caveat is that they are somewhat of an attractive nuisance. It's easy to get into designing beautiful classification hierarchies of dozens of different kinds of exceptions, each with their own specific failure information that is captured. In practice, I've found very little actual utility for them.
> My (more limited) experience is the same.
I agree. Creating hierarchies of error "types" seems fragile to me.
A better approach seems like having a short list of flat error types.
In cases where it might be useful to have a more specific error type,
instead include additional information on the error. So for example,
rather than having a NetworkResourceNotFound error, use NetworkError
but add a property which indicate what type of network failure. Not
sure if that was what you were proposing with the "proximate cause"?
More on that below.
I think the general direction that we've been heading lately has been
pretty good. I.e. error types like NetworkError, QuotaExceededError
are descriptive and have clear use cases for code to catch and handle.
On the flip side, the DOM also has some overlapping errors that are
too specific. IndexSizeError should simply have been RangeError, no
need to use a separate exception because it was an index that was
out-of-range, rather than some other type of argument.
Then there's a bunch of errors which are unclear why you'd ever want
to catch them at runtime. For example HierarchyRequestError,
NotFoundError, NamespaceError and URLMismatchError is unclear to me
why you'd ever want to catch. It seems useful to provide detailed
information so that a developer is helped with understanding what went
wrong, but that can be done through the .message property. TypeError
is probably fine for many of these. Though I sort of hate its name and
would be happy to see something more descriptive become available.
> However, I think the utility that the DOM specs try to capture with their errors is different than that captured by the exception hierarchy that one might think of when looking at them. Let me set the stage:
> There seem to be a few possible properties of an exception that could be varied depending on the situation:
> - The type. Typically this is very broad in ES, and in the DOM even more so (everything is `DOMException`). In ES this is the same as the `.name` property, but not in the DOM.
> - The category. ES has no such concept, but DOM specs use the `.name` property for this, with a variety of generic categories like "InvalidStateException," "NetworkError," etc. 
I'm not sure that there's a difference between these two. Or at least
I don't think there's a difference between the stuff that the DOM puts
in .name, and that ES puts in the classname. They express the same
thing just in different ways.
But that might not be relevant to your proposed solution.
> - The human-readable message. This is never specified, from what I can see.
I don't think we should specify the exact string that goes into these
messages. It's good if implementations are free to add additional
details if they see developers struggling with something.
But we could definitely do with more recommendations for
implementations about what information to include here.
> - The proximate cause. By this I mean the exact situation or line of the relevant spec that caused the error to be thrown. Every such cause should be distinguishable, even if they belong to the same category. Usually there will be a one-to-one correspondence between human-readable messages (or message format strings, with appropriate placeholders) and machine-readable proximate causes. I've seen this rarely, but some parts of Node.js use error codes for this.
> I have not seen much attention paid to the "proximate cause" idea in the wild. But I think it is perhaps the most important. Allen's point about hierarchies being largely useless resonates with me on the type and category level. That is, even the DOM's more fine-grained exception categorization is not that useful if I am trying to recover from a specific scenario. Whereas, the ability to specify specific situations to recover from would be much more fruitful.
> For a great example of these levels, see [recent discussions on the streams repo]. We have two situations: writing to a closing stream, and writing to a closed stream. On an ES level, these would end up as both `TypeError`s, as [discussed], with no further distinguishing information besides implementation-defined `message`. Working on a DOM level would be no different, I think: both would likely become `DOMException`s with `name` of `"InvalidStateError"`. What we really want is a machine-readable (and thus rigorously specifiable) "proximate cause" field that consumers could use to distinguish which situation they got themselves into, especially e.g. in logs. As-is, we just [folded them both into `TypeError`] with no detail on the cause, hoping implementations would be helpful about this.
> All this said, I think the DOM's categorization, and my proximate cause idea, are both trying to allow you to distinguish failure modes. You could envision, especially [with better syntax support], letting most errors bubble, but catching and handling `NetworkError`s with a retry. It would be even better, I claim, to be able to handle "network error from no network" separately from "network error due to server misbehavior", thus the proximate cause idea. This divide, between categorization and proximate cause, is part of why I think the DOM's exception categories evoke semi-useless exception hierarchies, even if they are aiming at a useful goal.
I don't fully understand everything you are saying above, but what I
do understand I agree with :)
I think a proposed solution would help clarify.
For your Stream example above I would propose using an
InvalidStateError but give it a couple of properties so that code
could understand what went wrong. Maybe a property which contains
"BaseWritableStream.write" to indicate that that was the function that
failed, and a second property which contains "closing" or "closed" to
indicate which wrong state was hit. This is definitely a very
unpolished proposal, but might illustrate something that could work.
> As you can see, I Have Opinions on this. I also know a number of community members who do as well. I'd love to discuss this further and perhaps pick up on that strawman idea you are suggesting, Allen.
More information about the es-discuss