Towards better error handling constructs

Started by
10 comments, last by Hodgman 10 years, 2 months ago

How is that different to throwing Barf() or Vomit(string reason) from a function?


Once you throw an exception, you're abandoning a call frame and all of its context. This allows callers to interact with the erroring call frame to recover.



In fact, going back to your opening, one of my 'issues' with exceptions isn't so much the burden of handling as it is that you end up often wrapping up all the code in a try {} block instead of just the bits you think will fail as it saves you jumping in and out of the block and mixing try{} catch{} groups across a function.


Perfectly true, and not something this would solve, unfortunately. I'm not sure of a good way to address this in general. The best I can offer is that you don't have to put the protection task inline; it could be defined as a standalone entity someplace else and just used by name:

protect
{
    // Fallible stuff
}
with PreExistingHandlerTask


I feel like that sort of syntax would lead to a very tight coupling between the function and the protect block. You'd have to know within a function, who would be calling it, and the calling functions would have to know all the possible 'exceptions'. Having to specify all the 'exceptions' isn't hard for a single function, but once they start getting nested...

It doesn't seem practical to me. Outside of trivial examples I can't for the life of me think of a good example. Even in the case of say an invalid file name in a file handling function, looping on an exception or sentinel return value would be easier. This also looks to me to be a maintenance nightmare.

Clever, but I question its practicality. But just cause I can't see its use doesn't mean there isn't one. Any good examples come to mind? Ones that wouldn't be trivial to code using exception handling or sentinel return values?


Fair points. It would certainly be open to abuse and creation of really nasty spaghetti logic, which is somewhat counter to my ideals.



Is error handling even a big deal?


It depends on what you consider an error, I guess. This is mostly useful around interface boundaries where you can't assume everything is always validated already. Suppose you want to validate a ton of user-generated data and then provide feedback to the user on how to correct any problems, such as what a compiler does. I consider this error handling, although that terminology can easily be debated.


It might just be because it's an unfamiliar construct, but I'd probably prefer that Vomit just be a lambda, which is invoked when the input is too high and returns a replacement value.
It's also not obvious to me why Barf is unrecoverable (halts the program?) but Vomit isn't? Is the idea that handlers with no ret-val are basically crash handlers, but ones that have a ret-val are lambdas?


Barf is not unrecoverable. It's specifically guarded against by the protector task. If I introduce a third error, such as Spew, which is not in the protection task, then it bubbles up the call-stack exception style, or terminates the program if nobody catches it.

My gut reaction against just passing lambdas around is that this turns into really deeply nested lists of lambdas in my experience. For instance, "modern" JavaScript - especially in non-browser contexts - is often many nested levels of lambda passing, sprinkled with passing functions by name instead of inline to avoid the syntax from becoming a (bigger) mess.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Advertisement

Barf is not unrecoverable. It's specifically guarded against by the protector task. If I introduce a third error, such as Spew, which is not in the protection task, then it bubbles up the call-stack exception style, or terminates the program if nobody catches it.

Sorry, I was thrown off by your terminology in the OP:

"barf."... is equivalent to an unrecoverable error.

So Barf is basically a regular exception -- control is 'thrown' from the panic site, to the "catch"/task, then back to the end of parent protect block, and resumes from there?
But Vomit is an "in-line exception" -- control is thrown from the panic site to the task, but because the task has a return value, it resumes from the panic site (instead of resuming from the end of the parent protect block)?
Is the lack/presence of a return value the mechanism that differentiates the two cases?

What happens when I write

panic => Vomit("Number slightly too high")

instead of:

p1 = (panic => Vomit("Number slightly too high"))

Vomit has a return value, but I'm ignoring it. This causes the throw/panic to look like an "unrecoverable error" where control will bubble up the stack, but will it actually be a recoverable error (basically a lambda) because Vomit returns a value?

If so, you'd ideally want to catch unused ret-vals as a compile-time error wink.png

This topic is closed to new replies.

Advertisement