7 ms·
No. This comes from a misunderstanding of checked exceptions. Checked exceptions don't mean I need to handle the exception right now. They mean I need to eithe
by invalidname 3y ago
No. This comes from a misunderstanding of checked exceptions.
Checked exceptions don't mean I need to handle the exception right now. They mean I need to either do that or declare throws. Declaring throws is fine it implicitly documents the code and enforces a similar requirement up the chain.
Checked exceptions aren't the default and shouldn't be.
- naasking 3y agoChecked exceptions should be the default, exceptions should just be rare. They are overused in almost all languages.
- invalidname 3y agoWhat about: NullPointerException? ArrayIndexOutOfBoundsException? IllegalArgumentException? UnsupportedEncodingException?
- naasking 3y agoWhat about them? If your language semantics are pervaded by exceptions, that kinda suggests your language sucks. If you're unlucky enough to find yourself in this situation, then make exception contracts simple to express and propagate.
- invalidname 3y agoThey aren't simple to express and propagate? What's so hard about declaring throws?
- naasking 3y agoYou have to think about exception polymorphism. If you don't have exception inference, you also have to think about manually propagating exception contracts. Declaring it once is not a big deal, doing it over and over again is super annoying.
- invalidname 3y agoThat's great, think about it... 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();
- MrJohz 3y agoNull pointer exceptions shouldn't exist in the first place, they're fairly trivial to avoid. Index out of bounds can be similarly avoided, although the cost in terms of economics is a lot steeper. I like Rust's strategy of providing a checked and an unchecked indexing mechanism, where the unchecked indexing mechanism typically crashes the program rather than just throwing an exception. 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.
- invalidname 3y agoNotice that parent comment was directed to a person who has the opinion of "everything should be a checked exception"... Not towards you. 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 ;-)
- MrJohz 3y agoI think almost everything should be a checked exception, with some possible exceptions (haha) being things like out-of-memory exceptions (although even then, with a good type system, I can imagine them optionally throwing checked exceptions). So it's definitely written at me! 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.
- bsder 3y agoOne of these things is not like the others ... 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.
- invalidname 3y agoFinally a person who knows Java ;-) 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.
- bsder 3y agoWhy is "UnsupportedEncodingException" an issue? 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.
- invalidname 3y agoYes. It's 100% an API problem...
- tremon 3y agoWhat do you mean when you say "exceptions should be rare"? Do you mean they should not occur frequently during runtime, or that it should be rare to see explicit exception handling in a piece of code?
- deleted 3y ago[deleted]
- naasking 3y agoSituations in which you must raise, propagate and handle exceptions should be rare.
- eptcyka 3y agoParsing input, handling I/O is a good chunk of what a lot of run of the mill software does. Since this is Java and exceptions are the main way of expressing fallibility, I’m at a loss as to what should code that needs to indicate fallibility should be using other than exceptions.
- naasking 3y agoReturn values obviously, with suitably designed method contracts. Some (maybe most) types of I/O errors are proper exceptions. Parsing failures are not.
- tremon 3y agoSo now you have two different error paths, one in which the file can't be accessed, and one in which the file can be accessed but the contents are corrupted? The developer must handle exceptions and they must still check if the function returned some sentinel error value? What's the point of forcing two different error paths on the developer?
- naasking 3y agoThey are different classes of errors, so you want different error paths. Parsing errors are common and should be handled locally with all of the usual logic. Exceptions are not supposed to be common errors which is why they trigger non-local control flow.
- matwood 3y agoAgreed. Too often exceptions end up driving normal control flow, when the should just be...exceptions.
- ajanuary 3y agoYou must have a real panic when you see a sign saying “No parking except weekends.” Weekends happen all the time!
- theshrike79 3y agoSo if you write code that handles dates you'd throw NotWeekendException to notify the caller that it's not weekend? Exceptions should be for exceptions, not control flow.
- kelseyfrog 3y agoI'm curious how this is possible? Exception throwing and handling is fundamentally a flow control construct. ie: throwing an exception must control flow. A counter-example snippet where throwing an exception does not control flow would be appreciated.
- naasking 3y agoHe means exceptions are not for common control flow that shows up everywhere, like a return value. You want an exceptional control flow change when your hard drive runs out of storage, but you don't want exceptional control flow change when your hashtable doesn't have the key you're looking for. The categories and frequency of these conditions are very different.
- kelseyfrog 3y agoHmmm, sounds like it just transforms into a Sorites paradox, no?
- 3y ago
- amalcon 3y agoThe fundamental thing an exception does is allow you to choose where in the stack to react to the exception. The fundamental thing a checked error does is force you to actually choose (even if that choice is to bubble all the way up and crash the program), rather than forget and end up with a bug. 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.
- naasking 3y agoI think there are idioms where your unchecked exceptions can be caught, like via some scoped context, like an actor.
- amalcon 3y agoYes, that's intentional. They are still exceptions because there are rare cases where you want to catch them. They're unchecked because it's not worth the hassle of declaring them everywhere. Python's KeyboardInterrupt is another great example -- it can theoretically happen almost everywhere, but most scripts don't have anything sensible to do with it and it will never happen in a daemon or GUI program. If you happen to be writing a REPL, though, it could be useful to just interrupt the last command.
- astral303 3y agoDeclaring throws end up leaking layers of abstractions in practice. Many thrown checked exceptions are not appropriate to pass above certain layers. It is often not OK to expose checked exceptions that are really implementation details up the chain—-it’s not a “similar requirement”—-it is exactly the same requirement and often an inappropriate one at many levels up the chain. If you just pass exceptions up in throws, you end up causing cascading checked exceptions chain changes and creating a bunch of noise, which is what many times people resort to doing in practice, because they don’t have the experience and it is very rare/almost never done to have any kind of automatic enforcement/linters/static analysis that prohibits implementation detail checked exceptions from propagating past above certain levels. It is almost never OK to propagate IOException, for example, past maybe 1 or 2 levels of private helper methods, almost never should be propagated past your class boundary and you have to think twice about adding throws to protected methods even, unless your subclass implementations are specifically implementing alternate IO calls. So yes that’s why checked exceptions have failed. And when you actually take this extreme discipline to your code, yes you end up with a ton of try/catch handling noise and a whole lotta new exceptions classes, and it takes similar extreme discipline to always avoid poor/improper try/catch handlers. Refactoring code across certain boundaries becomes a much bigger hassle, so your end up discouraging refactoring across those boundaries, which leads to code that is more easily stale and design decisions that are much harder to back out of.
- jayd16 3y agoYou make a great point about exposing the exceptions of library code. Why _don't_ linters deal with this better? It seems like you could throw up a yellow squiggly on any public method that throws an exception outside of its package or some such thing. I agree that it takes a lot of discipline to do this properly. Is that discipline alleviated with unchecked exceptions or would you say that doing things right takes the same work, checked or unchecked?
- invalidname 3y agoI think we're actually not that far off in our opinions. Throwing an exception is an implicit part of our contract, whether we declare it or not. Since any method can throw an exception that implementation detail is already exposed we just don't know about it. Yes. Propagating checked exceptions through a long chain is indeed a pita and as I said before, we need to be vigilant about using them correctly. But if I have library or infrastructure code that is doing IO/SQL, I still want people to know and handle the failure correctly. I think they were never picked up in other languages because no one likes to tidy their room. You want to write a unit test or a hello world and suddenly there's a checked exception... No fun. Also it was used for many APIs that people felt were built badly (e.g. the infamous encoding exception, URL format, etc.).
- crabbone 3y ago> Checked exceptions don't mean I need to handle the exception right now. Yes it does. You either use try-catch-catch-...-catch, or you append the new exception to the ever growing list of exceptions your function might throw. Even worse: when two functions called by the function you design can throw the same type of exception but for different reasons (eg. two functions cannot find a file, but those are different files), then, if you were a diligent and mindful programmer, you'd have to catch both and re-throw with new types, so that upstream could differentiate between the two failures, adding even more crutft into your code.
- invalidname 3y agoIf it's an evergrowing list then you're using checked exceptions wrong and they highlight to you the problem of too many responsibilities in a single application. Notice that IOException is one exception, you don't need to declare FileNotFoundException because it is an IOException. There's a similar hierarchy in an SQLException. That's part of the beauty of checked exceptions. If your throws statement becomes too long or its stack carries too deep then you know you have a problem. This would be hard to notice otherwise.