The choice about where to handle the exception is almost always a good thing, since it reduces boilerplate in dealing with that condition. The cases where it's not a good thing are when it's either impossible to deal with the condition at all (e.g. assertion failure) or where it must obviously be handled locally (e.g. errno==EAGAIN type stuff).
The requirement to actually make the decision is a good thing in some cases (e.g. I/O), but not in other cases (e.g. out of memory) because the overhead of making this decision so often is larger than the benefit of avoiding that class of bug. It's beneficial when it's something that can only happen while doing certain well defined activities.
There are also places where there just shouldn't be an error at all. E.g. null pointers shouldn't be an error; your program should be rejected if the compiler can't prove that this is impossible through static analysis (even if that just forces the author to write assert(ptr!=0)).
I guess I'm converging on a scheme that looks like: I/O errors are checked exceptions. Out of memory is an unchecked exception. Assertion failures are panics which cannot be caught. Division by zero is prevented by static analysis. I'm not sure what should be done with EAGAIN/EWOULDBLOCK type stuff -- it doesn't really feel like those should be exceptions, but introducing a whole new "error" handling idiom just for this doesn't feel great either.
What's so hard about declaring throws?
Let's say I have a Collection and want to do IO within that collection... If IOException was a runtime exception I could just write that code without handling it and an unsuspecting user of my new IOCollection would suddenly get an IOException. That means I need to explicitly deal with it in my class and can't add a serious exception to the behavior of the class.
Yes, it can still throw a runtime exception which is why the separation of the two is so important.
OTOH we have InputStream and OutputStream. Both throw an IOException for all their methods. So if I have one of them I should always handle the exception which is always the right thing to do...
But you might say, wait... What if I have a theoretical InputStream that will never throw an IOException?
Don't fret, we have that. It's called a ByteArrayInputStream and works roughly like this:
ByteArrayInputStream bos = new ByteArrayInputStream(new byte[100]);
int value = bos.read();
Notice I didn't use try and catch. Why? Because neither the constructor nor the read method throw an IOException which is legal in Javas polymorphism implementation. However, the following code won't compile without a catch exactly because I need to handle IOException for the generic case: InputStream bos = new ByteArrayInputStream(new byte[100]);
int value = bos.read();Illegal arguments can often be avoided in the first place with sensible types (although, like null pointer exceptions, this typically requires some language support), and unsupported encodings should be rare assuming widespread UTF-8.
So yes, I agree, those should all be very rare exceptions to find in a function signature.
Crashing the program isn't an option for a lot of applications. All those exceptions give me a runtime stack that I can log and an error that's very easy to fix quickly. The first 3 are runtime exceptions which means you won't know about them and don't need to write defensive code.
I love rust and its compile time checks. Checked exceptions are very similar to the concepts in rust, check as much as possible during compilation. However, this makes rust code non-trivial for some dynamic behaviors and sophisticated object graphs. There's a balance that we usually choose based on a languages target demographic. Rust is great for system programming, Java is great for enterprise programming. Both are very different domains with very different needs.
The discussion about null is a huge one and I'm already spending way too much time in HN comments. Forgive me if I don't open that damn can of worms ;-)
Rust is not the only language that handles these sorts of issues, and it's not the only way of doing that. OCaml also very neatly avoids most of these problems (again, indexing can optionally be checked), although it also includes exceptions on top of that. Even Typescript can be written in a style where exceptions, although interfacing with third-party code is a lot more difficult then. You can even configure the Typescript compiler to enforce checked indexing and prevent index-out-of-bounds errors.
The point is that there is nothing fundamental about these exceptions, other than that many languages have been designed with the assumption that they must be present and generally possible. But there's no reason to keep with that assumption, and we can write good code - and even good enterprise code, as demonstrated by Typescript - without those assumptions.
I find it disappointing that we expect so little from our tools that we tolerate these sorts of problems.
The first 3 need to terminate your thread because something is irretrievably incorrect. The only sane thing to do is stop.
"UnsupportedEncodingException" has lots of things you can do to recover. You can try different encodings. You can request retry of someting. You can check a CRC for corruption. etc.
So, the first 3 really shouldn't litter your code. The final one probably should.
For other readers the first 3 are all runtime exceptions and the last one is a checked exception that a lot of people complain about...
I think the last one is an example of why people hate checked exceptions and the solution to this was to create newer APIs that focus on UTF-8 that just don't throw that exception.
It should throw only on creation and then never be an issue from that point forward, no?
If it's throwing everywhere then, as you point out, that is an API problem rather than a problem with exceptions, themselves.
What's the point of forcing two different error paths on the developer?
Exceptions should be for exceptions, not control flow.
The categories and frequency of these conditions are very different.
If the expected operation is to return parking allowed status for a specified date or a weekday, I'd expect the function to return true or false unless the input is malformed (Actually I'd prefer it to return some kind of object that can give more context to the true/false, but I digress).
During normal operation it should never throw an exception. Because if I get an exception, I'll most likely either throw it up the stack or log an error, which will ping someone in PagerDuty if it happens too often.
[0] https://gist.github.com/timvisee/fcda9bbdff88d45cc9061606b4b...
What's normal operation? It sounds rather subjective.
It sounds like there are those who "expect the unexpected" and those who don't, and this plays out in weird programming language usage discussions. Among all the expectations that one could have about a program, people have the impossible choice of being either complete and inconsistent or incomplete and consistent in their operational definition of "normal". Is it not?
People keep parroting “exceptions should be for exceptions” like it means anything. Just because when it was first used it was given a particular name doesn’t really tell you anything about how it should be used.