item = db.findItem(id)
assert(NotFoundError, item != null)
render('show-item', item)
upstream middleware: try {
res = await downstream()
} catch(e) {
if (e is NotFoundError)
render('not-found')
else
render('internal-error')
}
you can do this with other patterns for upstreaming errors like Result + Rust's handy short-circuiting, but your example doesn't demonstrate much. what about internal errors like your store throwing a database error, your first example doesn't even have a store capable of erroring? your comparison incomplete. and surely you don't do all error checking at every callsite no matter which pattern you use.Which is essentially GOTO 404
A horrible idea that I see often in clever frameworks disobeying encapsulation and reasonable control flow in favour of magic
On the one hand, it's terribly convenient when you're writing the code.
On the other hand, as clon says, it breaks encapsulation. Not necessarily, but I've rarely seen these style of Web frameworks used in a way that doesn't result in network communication concerns wormholing their way through the entire codebase. And it sometimes does do weird things with control flow. Again, not necessarily but in practice. For example, JAX-RS frameworks do the exception mapping off in some magic location that's outside the part of the call stack that you directly control, meaning that customizing the logic requires hacking some fairly complex special-purpose mechanisms into the framework.
I suspect that there's a way to achieve convenience without relying on spooky action at a distance. But one could be forgiven for looking at the way popular frameworks on popular platforms work and concluding that cleanliness must be the price of convenience.
I agree that a database API should not itself throw an error if an empty result set is found. It may throw an error if there is an SQL error, though, and really should. When one gets an empty result set where this should not happen, for instance because a row was just inserted and one is in a transaction, the user code could throw an exception instead.
Making exceptions optional is something that I have indeed done in the past because some uses of the code could benefit from exceptions while other uses did not so much.
Some things are essential to the page we are rendering, and if they are missing we should 404.
Some things are ancillary, and if they are missing we should render a stub in their place. E.g., saying that a comment is from "deleted user".
Exceptions are a good fit for the former, where we want to unify every failure. They are a poor choice for the other case, where we want to treat each failure special.
Which case a given fetch represents is a choice that should be driven by the fetcher.
They have a type, make it say MissingUserException and the special place can handle it in a special way. Ensure it is a subclass of some general exception type you also handle elsewhere and you're good.
Often people throw generic exceptions instead of using the type system.
I like it. I wish that the concept of exceptions was closer to Panic because then we wouldn't be doing nearly as much flow control.
In the Rails world (where I live) looking top down, exceptions are handled via flow control or 500 pages. Anything that doesn't result in a 500 is effectively flow control.
While I agree with Rust's semantics, it generally isn't that bad to work around and knowing which is what.
That's JavaScript, right? That's probably not the best language to use to judge the concept of exceptions.
It's cleaner in languages like Java:
try {
Record record = store.findRecord(params.id);
render(record);
} catch(RecordNotFoundException e) {
render('record-not-found');
} catch (Exception e) {
render('unexpected-error');
}My response to that would be that if it isn't a normal thing for your code to encounter a record not being found, then you should handle that state in whatever you see fit, whether that's by passing down a value or object reflecting that error state or throwing your own exception(yes, I do think such a thing can be appropriate). I still don't favor the latter, but at least with throwing your own exception your application can catch it at a higher level and will be less likely to mistake it for another exception of the same class that should be handled differently. If you are using an SDK that throws an error for a "record not found" type situation, it would certainly work in your favor if your application really does benefit from it, but I think a lot of code doesn't actually call for such a thing.
But my point is that there's no reason I can think of that an exception can't be totally optional in these circumstances. I don't think it should be assumed that everyone wants an exception. There's a reason why we use conditional syntax like if-statements because, if used try-catch syntax as control flow for everything, the flow would be bass ackwards and hard to understand. In my opinion, the example situation I described is on the borderlands where an exception might make sense but probably doesn't actually make things better.
Every project is different, and people might choose to use exceptions a lot, which is fine for them. I've personally been better off not relying on the exception-based control flow taught to me when I learned Ruby, but that doesn't mean I would come into a project and remove everyone's exception handling.
And this is the problem with error checks: They're purely optional as far as the language is concerned, so we keep getting them wrong or forgetting things, even when we're being careful.
And you are the one who knows best if something is an expected failure, so wrap that up in a fetch_one_or_none() — as a matter of fact, most DB interfaces I work with have an API of the kind.
Basically, exceptions are a decent way to keep what might need to be a separate control flow, well, separate. And you can choose at any time to "join them in". As a concept, they are quite like interrupts on the very low (hardware) level, so you can learn only one "tool" to deal with them.
(And yes, I can trivially find cases where I want errors to bubble up when a record is not found, eg. a required configuration record or similar)
The first example: - no stack trace - loosing the specifics of error to troubleshoot.
The second example: - explicitly discards the stack trace - explicitly discards the error specifics.
Hence, if this is really what you want. fine, but in a lot of cases you do want to see stack traces and the exact reasons (and meta data why an error was thrown).
No offense, but I don't know where that idea comes from. Systems are checking for records all the time in ways where the absence of data doesn't necessitate throwing an exception.
For instance, a page, user, or piece of media on a website may have existed at one point but was since deleted, but still has a permalink floating around the net. Is it useful, in the case that someone clicks on such a link, to throw an exception when I can instead choose to render different page content when a record wasn't found? I'll never need a stack trace for that.
> They tell you lots: you asked for something your code path wanted and your request couldn't be satisfied. Also, you've been helpfully kicked onto the alternative execution path to handle that situation.
Why would I want a code path for expected behavior? I agree for cases like a failed connection, where the system is actually broken, but there isn't anything fundamentally broken about data absence.
> Also, you've been helpfully kicked onto the alternative execution path to handle that situation.
That's not only presumptuous, but if I wanted that to happen, I can do so myself.
> Exceptions are a hell of a lot better than littering your code with null checks or error code checks, especially when you forget one and get a null pointer error.
Hmmm...
or... I'll let people decide which one is better. I personally prefer the first one.