As an example, I wrote some very good C# code, carefully tested it, made it work flawlessly, then suddenly it started crashing. What happened? Someone made a change in a function I was calling, and it started throwing a new exception. This would have caused a compile error in Java, not a crash.
The list of recoverable exceptions that can be thrown by a method should be part of the contract. It should be written down by the programmer, and enforced by the compiler. If not, then to avoid crashing you would have to catch the root Exception class, which everyone agrees is a bad idea.
More on checked vs unchecked exceptions here: https://forum.dlang.org/thread/hxhjcchsulqejwxywfbn@forum.dl...
How are you going to write generic functions that take functions as arguments and re-throw the errors thrown by these functions, if you use checked exceptions? Will you require that the acceptable functions only throw exceptions that you like? -- Then your generic function is close to being worthless...
If exceptions are encoded in function's interface, then they have to be in ABI, but then you must have non-trivial types available when marshalling data between two components, so, you cannot serialize the communication using some protocol with a fixed number of types (eg. JSON and friends), because now you need to account for the infinite variety of exception types.
Because of at least these two things, what I saw happen a lot of Java / C++ projects (god blessed me with very little C# exposure) was that as soon as a developer encountered a function with checked exceptions, a wrapper was written which changed the type into a runtime exception. This is so because exceptions are supposed to be handled separately, and often the author of the function has no idea how they need to be handled -- so they want to concentrate on the main goal of the function. Once functions grow into garlands of try-catch-catch-catch-...catch the focus is lost. It becomes very hard to understand why the function was written in the first place, because the error handling takes over every other concern.
Constrained generic parameters are actually super useful.
> If exceptions are encoded in function's interface, then they have to be in ABI
They already are in Itanium
> you cannot serialize the communication using some protocol with a fixed number of types (eg. JSON and friends), because now you need to account for the infinite variety of exception types.
No, the only exception that arises is ser/deserialization error. You can also trivially represent an error in JSON using an object, which is exactly what JSON RPC protocols do. It's also never safe to throw across an FFI boundary and similarly nonsensical to throw across a serialization boundary, so I'm not sure why you'd care.
I've actually done this with an interface type parameter used in a throws clause in Java before, but I'm not sure how it interacts with module boundaries, it's kind of verbose, and it certainly didn't seem to be common practice. But it did work: the HOF-ish method that took a parameterized ThingFrobber<E> and used it to frob things unchecked would compile only if it also threw E, and the method passing the HOF a ThingFrobber (I think via lambda syntax) could catch the associated concrete checked exception type to sink it. IIRC I was using it to allow a checked early exit from a complex iteration, and it even propagated a slightly complex bound with multiple checked exception types more smoothly than I'd expected it to.
There is a philosophy that applies here: simple things should be simple, complex things should be possible. The scenario you're mentioning is not common enough that the language design should be centered around it.
If it has nothing to do with your code, why not either let it bubble up the stack or try/finally it to do whatever clean-up you need to do before re-throwing it and letting it move on to a global logger or similar.
If it is something you care about you can always catch the specific exception type and handle it.
Maybe whoever worked on the other function wasn't even aware it might throw that particular exception.
Granted I am far more experienced in C# than Java, but the idea that with every code change you potentially have to review and declare every single type of exception seems like madness ("what happens if we pull the cord when it's here, what type will that throw?").
Checked exceptions - a feature that seems to be a cost to the developer 100% of the time while being a benefit far less than 1% of the time - is a quintessential example of Java's tendency to cause developer fatigue.
The point is, if you don't need the rigor of a strongly typed compiled language, there are other languages you can use. Perhaps a bash script is all you need.
That's exactly it. A holier-than-thou stance, reprised by most comments in this post.
Checked exceptions were a PITA.
Someone has recommended to use another language if you didn't like it. Unfortunately my employer at that time had entered a partnership with Sun that prevented that :)
Another ideological prohibition: no pointers. Well, how do you assign event handlers then? People were extremely confused with anonymous classes. I got the reason instantly.
That's because exceptions are overused for not-truly-exceptional conditions. Languages that throw exceptions when a dictionary does not have a key you're looking for are doing it wrong.
If it’s really recoverable, then should it actually be an exception? How often do you see exceptions that are recoverable?
> If not, then to avoid crashing you would have to catch the root Exception class, which everyone agrees is a bad idea.
I disagree that it’s a bad idea, having a catch-all exception handler to avoid crashing the entire app is a good idea.
Java also has RuntimeException, and with it, a whole host of exceptions that may occur but are not declared. Should every function that uses the division operator be marked with `throws ArithmeticException` since it may end up being a division by zero? That would quickly become absurd.
That's the problem with exceptions in general. They're a hammer that makes everything look like a nail. People start using them for all kinds of control flow situations because they're more convenient than having to deal with a lack of type system support for optional values, etc.
You get to the point where you have a parser that is expecting a digit and it encounters an alphabetic character so it throws an exception!
Consider Files.createFile. It throws if the target already exists. That method has no way of knowing what the recovery is, however, the caller might.
It could be that the caller wants to explode, but maybe its part of some kind of singleton launch where the recovery is to pass some piece of data off to whoever created the file originally.
Maybe this is a bad example since there’s not much interesting in the return of Files. But it’s exceptional in that it’s an abort rather than a success.
That said, checked exceptions are still trash because they don’t work across thread boundaries or with futures. ExecutionException is the catch all baked into the design because they needed something. The idea could have been good though because naming error handling visible is useful. Java just has the misfortune of designing early before the kinks had been worked out and now we get nice stuff like Rust has.
E.g. a network call failing due to some IOException can be easily retried, that’s a proper error handling.
Most of the time, it’s either catch the exception, log it in a central logging system keep moving and have a central alerting system or catch the exception log it and crash the program.
Something relevant to the error condition, probably?
There may be some cases where the total set of possible errors is too large to meaningfully handle every one of them specifically (for instance if you call a high level GPU initialization routine that can fail in a myriad of ways) but that's not true in all cases. Maybe the new error was in fact recoverable, or maybe it calls for some more specific diagnostics.
At any rate I completely agree with the parent that changing the set throwable exceptions should be considered a breaking API change and should be enforced by the compiler. If an API method wants to future-proof and be able to throw anything, it can declare just that and everybody will know to expect the unexpected.
That's why I vastly prefer Rust's Result<> system which does most of what exceptions do and with fairly similar ergonomics but with normal return values and all the type checking that it involves.
See https://learn.microsoft.com/en-us/dotnet/fundamentals/code-a...
I call BS here. I've worked in a few Java projects, and in every single one, the people changing the method in question would have thrown RuntimeException to stop the compile errors.
If RuntimeException was checked, you might have a point, but given that there's a class of unchecked exceptions, you're relying on discipline instead of the compiler anyways.
Seriously, Java doesn't force bad programmers to write good code. It just enables good programmers to write good code.
We all do.. its always that other guy who is at fault :)
Each non-leaf statement is a composition of leaf statements. Therefore every exception can be determined, and that's our checked exceptions would work; work at the exceptions and check they match the list.
Therefore even without checked exceptions the compiler could provide the exceptions you want and you can check them or not if you please, so get the best of both worlds. All without forcing people to give an explicit list of exceptions everywhere. Forcing checked exceptions is not going to be popular, and much of the time is counter-productive.
Also someone broke your Liskof Substitution Principle which may be your bigger problem.
It's a bad idea to catch an unexpected exception?
IMO the entire concept of "unexpected exception" is bogus in and of itself. That's like calling "exit(1)" in your code when something goes wrong instead of using proper error handling facilities.
Admittedly it's very convenient for small scripts where proper error handling might be overkill, but for any serious application it's a massive footgun IMO.
Anyway, I sympathize with this thread's OP, as I've had exactly the same problem with C#. In the end, I came to the conclusion you kind of do often have to catch every exception, because you can't trust the functions you call, and the documented exception lists for framework stuff are not always accurate. Safest is to put each call in its own try...catch block, and do any property accesses outside the try...catch block so you find out about the NullReferenceExceptions.
This is annoyingly verbose though.
It also does assume your setters and getters don't throw anything.
I mostly liked C#, but the exceptions aspect is not good. Similarly, I've generally despised working with Go, but the way it deals with errors is not its worst feature! (You do have to pay attention to the linter output though! It's the same err variable every time, so the compiler's fastidious checks for variable use are always foiled...)
Another issue is that you are not allowing higher layers see the exception, even though they may have logic for recovering from the exception. To see the exception they have to now fix your code by removing the catch-all.
Checked exceptions are unpopular since no one likes responsibility. But they are great when used right. Calls that must have proper cleanup after them e.g. SQL, IO are checked. The fact that this must be communicated via interfaces is hugely important.
There are "weird" problems such as stream close() throwing a checked exception. That's an API misbehavior that has a workaround thanks to try-with-resources.
Go's exceptions have the same unfortunate property as Java's checked exception. And that's what makes Go's code atrocious. Every other line you see something like:
if x, e := f(); e != nil {
...
} else {
return y, e
}
It makes it very easy to make mistakes when you have to write a lot of repetitive code. You make typos, and because they often land on the "bad" path, they aren't immediately discovered. You have to memorize the state of your function wrt' variable initialization, because now you cannot automatically initialize and destroy them all together. You need to create a lot of helper variables whose purpose is only to transfer return value from one function to another...If you think that you want checked exceptions, then you don't want exceptions at all. You are denying them the very purpose they were created for. But there are alternative ways to deal with unexpected events in program execution. Monads would be one of those. So... maybe just don't use 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.
Wait what? Here's where you lost me. You don't "throw" errors in Golang. Instead it's common practice for functions to return multiple values, one of which can be an error. Errors are just structs that implement the Error interface.
The language (and the compiler by extension) doesn't make you explicitly handle errors. On the contrary: you can choose to ignore errors altogether.
On the other hand, a checked exception in Java is one that MUST be either caught or declared in the method in which it is thrown. Code that fails to do this won't compile. You're completely wrong here.
Why? In C# you use a “using” block and whether your code exits the using block successfully or with an exception, the cleanup is automatic.
RAII has been around at least since the 80s with C++.
They also force you to declare that an exception is thrown if you don't want to handle it in the current method. That's important as the signature of the method carries an important failure that can't be dismissed and is enforced by the compiler.
> Null is fast. Super fast. Literally free.
Yes and: The JIT will optimize away Null Object method invocations.
All my composable classes have a Null Object. No null checks means concise iteration (eg graph traversal). Eliminates NPEs. Just as fast.
Win / win / win.
Optionals (classes and operators) continue to be turrible mistakes. So much unnecessary effort, so little benefit.
Ditto @Nullable and @Nonnull.
Someday, maybe, I'll make a javac compiler plugin which autogenerates Null Object implementations and converts "Node abc = null" to "Node abc = Node.DEFAULT_NULL_OBJECT_INSTANCE".
Even when the exceptions is theoretically recoverable, it has to get propagated up properly to the caller who should be handling recovery from it. But checked exceptions don't propagate sanely through executors and across RPC calls, so good luck with that.
The try/catch construct require too much code for the common propagate case.
It would be nice if Java had a similar construct for error handling.
Still, it's too bad the error type must be a throwable. I kind of wish it could just be a plain type so you can error or cancel without generating stack traces. Awaiting a failed task could still throw.
Would be a nice perf boost. As it is now, you don't want to actually cancel or fail a C# task in performance critical code. You need to successfully complete the Task and return an error, which is pretty confusing.
Proliferation of types is an issue in Java, but the whole language has that problem. It's not just exceptions.
In that sense, they are quite useful. Saving the file generated an exception - instead of suddenly closing the application, display an error. Or maybe a database is not accessible anymore. Instead of suddenly ending the service, trigger a controlled shutdown (log, send alerts, etc).
I don't think anyone would expect exceptions to propagate through RPC calls. A call fails, probably containing a description of the fail. Why should it propagate?
There are ways to handle errors and return errored RPCs without exceptions, such as internal error codes. But error codes are just as irrecoverable as exceptions.
But in the world that most Java devs live in, which is various flavors of RPC server, failing requests is fine. If lots of requests fail, your monitoring infra should page someone, and that someone will go log spelunking and figure out what's broken.
Very occasionally it turns out that the thing causing the RPC failures is a recoverable exception, and then you should wrap the problematic stuff in a try/catch block. (Often you'll wind up having to detect the recoverable error case by conditioning on substrings in the exception message, which the library owners will arbitrarily change in future releases. So make sure to write regression tests so you'll catch this when you upgrade the library. Java is fun!) But 99% of the time the failure is "network is busted" or "config was invalid" or "hard disk failed" and you should not be defensively programming against all those possibilities.
I keep returning to something about error handling that bothers me. People argue what the proper way to handle errors is without really considering that it's highly context sensitive. Which makes me think you should be able to pass an error handler down to lower level functions that tells them what to do when something bad happens. Sort of like recent ideas where you pass functions a allocator instead of them calling malloc() directly or whatever.
The argument that some exceptions will always be runtime (like oom) so checked exceptions are flawed is harder to argue against but I would also say it's a matter of opinion whether you feel like it's worth dropping entirely.
The issue though is that forcing people to handle irrecoverable exceptions is just kind of dumb. If I do file IO, I know it'll barf sometimes. Sometimes I care, but the vast majority of the time I don't.
A lot of Java servers have RPC endpoints that basically say "write X to file Y" or "read X from file Y" and if the S3 connection is busted, there's nothing the server can do about that. Someone has to go log spelunking, figure out what's wrong (network is misconfigured, AWS is having an outage, whatever) and fix it. So it is bad API design on Java's part to make every single one of those RPC endpoints wrap every single thing that does file IO in a try/catch.
public interface ExampleInterface {
void foo() throws IOException, AnotherException;
}
was not covered: is this also not supported?That would solve a lot of use cases if inheritance is respected.
The interface can't know every exception that an implementation might throw, and using your example, some implementations may not use IP at all and won't throw those exceptions.
It seems like you'd end up needing to list every possible exception on "foo() " when defining the interface, and then handle every possible exception in any code that uses "ExampleInterface".
At that point, it would be better to not annotate exception information at all.
No, as you'd force the implementation to catch their implementation specific exceptions and rethrow them in the way defined by the interface.
That's really the crux of it, when I call foo(), I expect to see a FooException when something goes wrong, since that's the only one I can meaningful react to. If I get an ImplementationDetailException that I never heard from and that isn't specified in the interface, how am I supposed to react to that in a meaningful way?
If exception are supposed to be used for error handling, you have to actually report and handle them in a well specified manner, you can't just treat them as a slightly less crashy version of a SIGSEGV.
There is not much difference between this and using error types, except the syntax sugar.
Let's say that the creator of the interface allows IOException, but the backend of my implementation is a database so I have SQLExceptions, same issue.
Is the creator of the interface supposed to add every checked exception in the standard library? Ignoring that this still isn't all of them, then the caller is hosed because they have to handle (or rethrow) every single one of them which is no better. So they probably go "fuck it" and just handle / rethrow Exception. At which point you can just do that on the interface, and it's not helping anyone, and you're better off just not saying anything.
Checked exceptions simply don't mesh with the language: because the language provides limited to no way to abstract over them they're an issue every time you're trying to be generic over anything, the only situation in which they kinda sorta work is if the entire callchain is concrete, which not only is very limiting but it's very much not idiomatic.
The creator of the interface should decide on a generic exception type:
public interface UniversalStorageInterface {
void store() throws StorageException;
}
Then, in the present, the FileStorage can define (and throw) a FileStorageException (inherits from StorageException), and the SqlStorage may define (and throw) a DatabaseStorageException (same).In the future, where we might want to store everything on a (non-existing yet) Cerulean backend, we would then define (and throw) a CeruleanStorageException (again, an implementation of StorageException), and our basic Interface would not need to change. We would also have no need to recompile FileStorage or SqlStorage (or proprietary SteelBlue storage where we have no code).
If the interface does not provide exception types for all cases so you are stuck rethrowing your error as a wrong one, and I'd say that means the interface is bad but not checked errors are bad...
It would probably be handy if someone wrote a pamphlet on "Refactoring Exceptions" to give teams the confidence to refactor exceptions out of their code. I'll offer $20 to Martin Fowler to write such a thing.
Also, the point about multiple return types is pointless — it already has Optional, a proper Return type is completely feasible to implement and use in Java. So is a Pair<A,B> if tuples are what you mean. Some are just not part of the standard lib by default.
If backwards compatibility wasn't a concern, checked exceptions would probably go away in a version or two, and we'd have some kind of monadic error type instead. But Java takes this seriously, and the standard library itself has methods with checked exceptions, so the timeline to go from checked exceptions to something else is very long.
Yet it is rarely done. Lots of boilerplate to replace what is easily done in other languages. If java provided a native tuple type, new patterns would appeat. As it is, too many lines for 1 off returns. Send off a serialzed json and deserialize it later. Easier than specific dtos.
Yeah, that doesn't sound disjoint /s
Relevant JEPs:
- 441: Pattern Matching for switch
- 440: Record Patterns
- 409: Sealed Classes
- 395: Records
It is kind of similar to Scalas version of pattern matching with case classes.
> Functional error handling, using Option and Result types, is rapidly becoming the standard operating procedure in essentially every language, because it relies on nothing but values and types. They are more of the same, and so they fit right into the existing language machinery.
I agree that Optional types are better than checked exceptions, but they are worse than union types. A function with the union return type String|Error (read: String or Error) is allowed to return another function with return type String. Similarly, a function which accepts the type String|Error as a parameter also accepts parameters of type String. With Option types this doesn't work. Option<String> and String are incompatible types, so code has to be rewritten if the types change.
Personally, I would go so far as to say that union types should replace exceptions in general, checked or unchecked, as well as any implicit nullabiliy, which can be replaced with the explicit union type Foo|Null.
(Although some special syntax for handling Foo|Exception or Foo|Null is probably a good idea, as "error" and "nothing" are pretty general categories.)
I have been thinking about this in terms of a data oriented programming language like clojure, but having types/schemas. If you use Union Types for something like getting a value out of a map for a specific key, then if you knew the schema of a specific map you could reduce the return type from {T, Error} to just the type of the value T that you know is there.
Basically a sufficiently smart compiler with the necessary information could make you not have to deal with errors at all in certain cases. With Result/Option/Maybe this would not be possible. It would always infect the entire callstack and you would always have to deal with it.
The other issue is that the language needs to have support for union types rather than merely sum types. And there's a good reason almost no statically typed language has support for union types since it removes provability and it's not something that can in easily be statically decided, requiring dynamic type tags, but then again, that's what sum types effectively are as well.
Typed Racket is statically typed and has union types, but it's an extra layer on top of a dynamically typed language, so the type tags where there already, but it's not easy to erase type data at run time not to implement.
In your example of “String|Error”, it essentially requires that every String is now tagged at runtime with tag information about it in practice.
For what it's worth, I learned about union types from Ceylon, which was statically typed.
In C++ I find it quite infuriating that try{} opens up a new lexical scope, which means you can't construct something, check for errors and move on, since the act of adding a try{} means you'd call the destructor of the thing you just constructed. Meanwhile surrounding a whole block of code with a try{} means you no longer can tell where the exception came from.
You technically can with a small workaround (demonstrated below), though I personally wouldn't use this approach.
void foo() {
auto value = [] {
try {
return Foo();
} catch (...) {
// Something here
}
}();
value.do_something();
} auto value = try Foo("will fail")
catch(...) {
return Foo("will work"); // or throw something else
};That’s an interesting point. I’ve never considered that because I haven’t worked with classes that regularly throw in their constructors.
What situation do you have where 1) you are constructing classes that regularly throw in their constructors and 2) this is a recoverable error that 3) should be handled at the point where the class is constructed instead of some outer point?
QImage image("filename.png");
is pretty common. Qt solves this by not throwing an exception and using a Null image check: bool QImage::isNull() const
but that throws all the extra information that an Exception could provide away.This slays me.
The try, catch, and finally blocks should be just one lexical scope.
If, as is kind of the point of RAII, your constructors do some initialization and that initialization may fail, then:
If you have a single object that may fail to construct then maintaining it in scope for the catch block doesn't make sense; it's not initialized.
If you have several, then you still can't maintain them in scope, because you don't know which ones are actually valid.
Yes there are other patterns that wouldn't have this issue (acquiring resources outside of the constructor), but then you can just declare your objects outside of the try-catch block.
To me, the difficulty with exceptions is when writing a method that needs to throw an exception is deciding whether it should throw one that is actually checked. In fact, read a few of years ago most exceptions should actually be unchecked, but at least then, this was still greatly up for debate.
... as I type this, am realizing having the option of unchecked and checked exceptions is nice in that they reduce the amount error code you have to write. Because by throwing unchecked exceptions, this is error handling that all the entire calling code-chain doesn't need to deal with. The obvious situations are system errors like out of memory errors, missing resources... but also those programmatic errors that are unexpected, like array out of bounds, ClassCastException.
Also, the calling chain then has the option to handle it if it's needed. For instance, if you're writing a low-level messaging queue that needs to report system errors (btw, this was a real situation that happened to me). Just my two cents.
This however runs into issues. One really annoying one: Checked exceptions are part of your API. If you forget an error condition and want to introduce a new exception for that, well guess what, that's a breaking API change. There is no easy way to evolve an API with checked exceptions, because changing them requires new major versions, because it prevents code from compiling.
On top, if you want to encapsulate your API properly, you end up with a lot of boilerplate. Once you're using a library, and that library has checked exceptions, you either have to base your own public API on the API of that library - meaning you can never replace it - or you have to start wrapping all manner of errors into your own checked exceptions. I've kinda done it as an experiment some time ago, but that just ends up with so many exception types and so much error wrapping it's kinda ridiculous.
And then you end up with the sad truth on top to be honest: Most error checking is rather brute and clumsy. In most cases, I just let exceptions bubble up because my intermediate function can't really do anything about it. As a distant second, you catch all errors, shove them in some kind of error reporting, reset the system and continue trucking. As a somewhat similar third, I dissect errors in CLI tools to create some useful error messages. And only them I might start caring about some specific errors, but that's pretty rare if you're just running some REST-based business logic.
All in all, it's a good idea, but the implementation results in a lot of API churn or boilerplate for something that's not used much in general.
This sounds like a good thing. Throwing a new exception is a breaking change whether it's enforced by the compiler or not. Otherwise, callers of you API go from being exception safe to not being exception safe. It's strange to me that you would want to squirm around that just to avoid bumping the version number.
You can gradually increase the granularity of unchecked exceptions using subtypes. For example I might initally have AnythingFailedDuringTheQueryException with a Subtype of QueryPreparationException. If I introduce new subclasses of QueryPreparationException to increase the precision of the error reporting, all existing catches for QueryPreparationException will still function as they did before. A "NotEnoughParametersException" subclass of a QueryPreparationError is still caught as a QueryPreparationError. Only if you introduce catches for the new more granular unchecked exception, your codes behavior changes to use the new behavior.
That's a very clean way to improve error reporting in a backwards compatible way.
Handling effects and effect polymorphism in programming languages is an active area of research and there are some new languages that try to approach the problem (ie. Koka).
Haskell has several effect libraries (effectful, cleff, eff, polysemy) that look quite nice.
Idris with its dependent types allows precise definition of effects in function signatures.
But that is true in both cases. The problems with checked exceptions come on top, namely that you cannot use all the regular value-machinery to transform and manipulate the result of a function-call.
We are saying the same thing, aren't we?
But it is not only about exceptions. Other effects have the same problem and you need a mechanism to define effect polymorphic functions.
https://www.artima.com/articles/the-trouble-with-checked-exc...
> Anders Hejlsberg: Yeah, well, Einstein said that, "Do the simplest thing possible, but no simpler." The concern I have about checked exceptions is the handcuffs they put on programmers. You see programmers picking up new APIs that have all these throws clauses, and then you see how convoluted their code gets, and you realize the checked exceptions aren't helping them any. It is sort of these dictatorial API designers telling you how to do your exception handling. They should not be doing that.
> Concretely, checked exceptions in Java failed because Java lacks “throwingness polymorphism”, if you will.
I don't understand. Every "missing feature" the author asks for is, in fact, present in Java. The example below just about sums it up.
@FunctionalInterface
interface MyCallback<X, Y, E extends Exception> {
Y apply (X x) throws E;
}
class A {
<X, Y, E extends Exception> Y runCallback (MyCallback<X, Y, E> func, X x) throws E {
return func.apply(x);
}
} abstract class GenericException<R> extends Exception {
R reason();
}
As it is, Exception is a "special" type that isn't allowed to be used like normal Java types, because Java generics are a leaky abstraction introduced after checked exceptions, and the JVM cannot deal with type erasure in catch blocks.just because theres the possibility of a throw in the signature, does not mean it has to be done in a concrete implementation, and if implementations are really to be exchangable, it has to be handled in case implementation changes.
> What would it even mean to write something like throws *?
Roughly similar to "throws Exception" or "throws Throwable" - that it may throw whatever you have under the sun?
> Concretely, checked exceptions in Java failed because Java lacks “throwingness polymorphism”, if you will.
it does allow for extending exceptions, so an interface can define some exceptions that are basic, and implementations can then choose to extend those to provide MORE detail if needed.
> Was Java wrong to add checked exceptions? No. They took a risk and it didn’t pay off.
Uhm.... Im gonna go ahead and use some other opinion on this
As many language feature, checked exception are often misused and/or misunderstood. Too many time I have seen people blindly propagating checked exception above, and you end up having a controller method exposing a SQLException. Then for sure, it is better to use unchecked exceptions overall
Checked exceptions force you to do more work, but it's essential work to make your code more robust.
Replacing them with runtime exceptions gives you the illusion of clearer code, but all you have is actually code that is more likely to crash.
Like if you try to construct a URI based on a configured URI string, and get URISyntaxException... wtf should you even do? You can't recover, because your code shouldn't be mutating config. Similarly with basically any JSON parsing exception. And most disk read/write exceptions.
And in modern applications the caller is probably on the other side of an RPC call, and checked exceptions won't propagate sanely across the RPC anyway.
Oh dear. I have a slew of current counter examples on my machine, in GitHub, etc.
Either carelessly untrue or else annoying hypebole.
Made the rest very hard for me to read.
However, there is an exception (pun intended) to this: inline lambdas. You can use lambdas as variables, pass them as parameters, etc. And it makes sense for them to retain the interface if needed, but (at least on java) when you are doing a .map(v->parseInt(v)) checked exceptions can't escape the lambda, and that's probably the worst problem of java lambdas.
You can avoid this with custom interfaces where the exception is a generic parameter, but sadly native ones aren't defined like this (yes, exceptions that a function throws can also be defined with generics, and the compiler will automatically use the more restrictive of the lambda code! )
interface NonThrowingAutoCloseable extends AutoCloseable {
void close();
}
You can also declare that you throw a generic exception type interface ThrowingSupplier<E extends Exception> {
void get() throws E;
}Investing in (checked) exception safety is not as valuable when anything can throw an NPE.