I don't think exceptions should be used if they are caught and then retried or transformed. I don't think exceptions should be used in library code, except of course for unanticipated behavior such as hardware or network problems. In these cases it's better to use a result object, which better forces the library consumer to inspect what is being returned, rather than pass on what is returned and (maybe?) handle exceptions on errors. Ideally the library catches unanticipated errors and never throws, but those are edge cases that are probably not worth the investment by the library maintainer to handle. Development effort across the ecosystem is probably minimized when library consumers handle exceptional situations themselves, which they may have more insight into if their own infrastructure is causing the exception.
You’re better off using Exceptions
121–130 of 242 posts
Re: You’re better off using Exceptions
#122When I moved to Rust the constant error wrapping or converting annoyed me. But after using it for a couple of years it turned out to be a huge blessing. Quite often I need to know exactly which error messages will be thrown so that I can do things like internationalization. While exceptions are quite convenient for prototyping for production I’m now firmly in the typed errors camp.
I don't know Rust at all but what you're talking about sounds equivalent to (much maligned) Java's checked exceptions system? I never understood the hatred checked exceptions received especially from the younger crowd. I still write Java at work and I still use checked exceptions whenever they indicate an error condition that must not be ignored by the client code. Many new to the project developers hate me for it in…
Here's a random rant online that ends up pitching the more common solution of unchecked exceptions + exception rewrapping: https://phauer.com/2015/checked-exceptions-are-evil/. And of course, in Rust, the rewrapping is basically forced upon you making for a really nice error-handling ecosystem.
Re: You’re better off using Exceptions
#123Earlier quoted context omitted.
> It's not a normal thing for code that needs that record that wasn't found. 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.…
Well, in real code you are likely bubbling up an error in some way for your 404 Not Found and 500 Internal Error handlers to kick in. 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 l…
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
Re: You’re better off using Exceptions
#124Earlier quoted context omitted.
> Most performant: never. I disagree. An exception can be faster than manually unwinding a set of nested scopes.
Most exceptions, at least in the JVM where this is commonly debated, will incur a penalty of building up the exception and unwinding the call stack. What aspect of performance are you using as an example?
Re: You’re better off using Exceptions
#125Earlier quoted context omitted.
> A record not being found is a normal thing! It's not a normal thing for code that needs that record that wasn't found. > They literally tell you nothing and there's no way to solve them without catching/rescuing them. A null value, a plain "error" object, or an error argument in a callback would have been sufficient. If I need an exception to be raised for this kind of thing, I'll do it myself. They tell you lots:…
> It's not a normal thing for code that needs that record that wasn't found. 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.…
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');
}Re: You’re better off using Exceptions
#126The one parallel to (actual) exceptions is the author is arguing for a constrained subset of error states rather than a catch all Error type. This isn't a new idea. Java essentially does this with checked exceptions. C++ does this where you can declare what exceptions can be thrown (which you should basically never do).
So I did Java for years. Java had several Grand Experiments, one of which was checked exceptions. Despite it still having some fans I think the general consensus now is that checked exceptions were a Huge Mistake [tm] for many reasons (eg leaking implementation details, cluttering your API the whole way up).
One anti-pattern I see with exceptions is people using them for control flow. For example, dealing with a ParseException in Java when parsing numbers [1].
This of course falls into a religious argument about what an exception actually is.
For a few years at Google I wrote Google's flavor of C++, which I actually grew to really like. It is pervasive that any method in Google C++ returns a util::Status. This is much like Go error handling but (IMHO) better.
For one thing, it's a compiler error to ignore the result of a util::status (you can call .IgnoreResult() if you really want to ignore it). I think this is a much nicer and safer default.
You return things with a util::StatusOr templated union type that has the same semantics. There are even macros (that were somewhat controversial) to reduce boilerplate to do things like call a function that returns a StatusOr, assign the result to a variable if it's OK or return the error if it's not.
There are of course utility methods for adding context to a Status(Or) you're returning.
So all this came about because Google C++ strictly prohibits exceptions. This is a historic decision that's probably impossible to unwind at this point and honestly I don't think there's a strong motivation to change it.
IMHO exceptions are a false economy.
This is one of many things I like about Rust. Rust's enums are kind of the next evolutionary step for this. It's a compiler error not to deal with all options, there are constructs to reduce boilerplate and you can pass values.
[1]: https://stackoverflow.com/questions/8286678/parse-string-int...
Re: You’re better off using Exceptions
#127Earlier quoted context omitted.
In Java/C# land exceptions are EXPENSIVE. Like magnatudes more expensive. You have to build a full stack trace etc. Removing places in the code where it is "Throwing exceptions for non exceptional circumstances" has a dramatic performance increase benefit.
C++ too. I suspect it’s true in almost every language. Maybe not Python? But Python is slow regardless.
But C++ does not provide a stack trace.
Re: You’re better off using Exceptions
#128Earlier quoted context omitted.
> A record not being found is a normal thing! It's not a normal thing for code that needs that record that wasn't found. > They literally tell you nothing and there's no way to solve them without catching/rescuing them. A null value, a plain "error" object, or an error argument in a callback would have been sufficient. If I need an exception to be raised for this kind of thing, I'll do it myself. They tell you lots:…
> It's not a normal thing for code that needs that record that wasn't found. 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.…
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.
Re: You’re better off using Exceptions
#129Earlier quoted context omitted.
That case is even much more exceptional: where the record existed when you checked but didn't in the millisecond afterwards when you actually opened it. But if that is common, then I would guess a concurrent check for existence and open if it is might be necessary.
That’s a very, very common source of subtle bugs (often exploitable as security bugs) that are hard to spot and hard to test for. It’s good practice to design APIs to make race conditions less likely, by explicitly not splitting operations across multiple calls. Separating “exists” and “get” into separate calls is a disaster.
I guess databases and file systems are different because concurrent access is the norm, not the exception, and APIs should be designed accordingly. But for most of your in-memory structures, you probably don't want them dealing with concurrency because they aren't going to be able do that very well anyways (instead, deal with concurrency outside of the data structure and have them throw when inconsistencies from bad concurrency policies arise).
Re: You’re better off using Exceptions
#130Earlier quoted context omitted.
The biggest issue I've seen with exceptions is that people bring up the issue when they are abused. I SORT of agree with you that an 'Record not found' exception is an abuse. This should probably be a null-object like an Optional ALA Java or Scala. They are trying to avoid returning null in this scenario. Unfortunately, if the API is designed poorly you're stuck with it. I can sort of understand why people hate check…
Nulls are also called the billion-dollar mistake (and that was decades ago; it's much more than that now). Both nulls and exceptions are ways of trying to make the main line of processing clear, while handling other lines in structured ways. There's no one-size-fits-all solution. In a lot of ways, the best response to "record not found" is that you get the same result as finding one, except with zero answers. That me…
At the same time before NULL, devs used to use "guard values", so NULL is really just a convenience.
for instance, just to illustrate what I'm saying:
let NULL = {} /* should every lib define its own null value? */
function GetOneRecord(){
let dbResult = queryRecordsFromDB();
if (dbResult.length === 0){
return NULL
}
return dbResult.records[0];
}
Optional types, are better, but one needs functional programming features for it to be really useful.What's people opinion on zero values by the way?