https://gen5.info/q/2008/07/31/stop-catching-exceptions/
Checked exceptions don't cause people to write good error handling code, they just cause a crisis for no good reason when you are writing code that people will answer with some lame answer like
try {
action();
} catch(ActionException x) {}
or try {
action();
} catch(ActionException x) {
throw new RuntimeException(x);
}
or try {
action();
} catch(ActionException x) {
throw new SomeExceptionThatWillCauseACrisisLater(x);
}
when you really should be doing something like try {
action();
} finally {
makeItRight();
}
and finally deal with the exception at the end of the "work unit", seehttps://gen5.info/q/2008/08/27/what-do-you-do-when-youve-cau...
Look at the JDK 8 streams library of an example of a library that (a) is awkward as hell because you can't
stream.map(this::someMethodThatMightThrowAndException)
but (b) still handles errors improperly. Some versions of Lisp have a much better approach described herehttps://gigamonkeys.com/book/beyond-exception-handling-condi...
in most languages what you can do is build a framework that controls execution in such a way that (in the context of a stream of work units) it can "separate the code that actually recovers from an error from the code that decides how to recover" as that article says as best you can.
That's not a valid argument. Lots of people believed the earth is flat but we now know that's not true. Did you read the article? Explain why it is wrong.
It didn't, and they aren't bad.
Checked errors are good and are so important to have program correctness, but you need a way to easily escape them when necessary. Rust and Swift have both have realized this. Rust provides ? and Swift has try!, Java needs the same.
> Look at the JDK 8 streams library of an example of a library that (a) is awkward as hell because you can't > stream.map(this::someMethodThatMightThrowAndException)
This is again Java the language not providing the necessary tools. Checked exceptions can work across high order functions: https://docs.scala-lang.org/scala3/reference/experimental/ca...
(1) We were trying to parallelize an easily parallelizable task in Scala. Except it would only use a fraction of the CPUs available and didn't always give the right answer. In three days I was no closer to getting the Scala solution using all cores, I was able to do it in three minutes with Java Executor.
(2) I saw a somewhat larger code base that implemented a data processing pipeline. I was told by the eng manager that (a) we do code reviews and (b) we use monads for error handling. I guess we did, except it was the monad equivalent of
try { something() } catch(SomeException x) {}
most of the time which, once more, fits pattern of people writing exception handling code to silence the compiler.In principle something like monads could let you implement more complex error handling strategies (like Lisp) but so long as "a monad is a like a burrito and a computation is like a graph", monads will be underpowered. Note you can use polymorphism for handling errors in Java too, for instance
interface FunctionThatThrows<In,Out,X> {
Out applies(In arg) throws X;
}
JDK8 streams could have done better with the tools it had.All of the above examples of custom exceptions are great examples of things that should never be handled by exceptions. If the author was using a library that really threw exceptions like this, then it would probably be best to just find a new library which isn't arbitrarily throwing exceptions to create control flow logic.
Real life does not respect encapsulation, that is, it is not really your application's business to know that such a thing as a SQLException exists, other than how to log it, if SQL is hidden beneath a DAO layer. I mean, just because the beautiful architecture of your application doesn't have things like backhoes cutting through fibers in it, doesn't mean those things can't happen to you. There are a lot of things that will happen to your application that it can't understand and the best it can do is: (i) protect its own integrity (use finally) and (ii) abort, retry or ignore and (iii) hopefully have the wisdom to choose the right one of those.
So you prefer to check for errors after every function call? That's what you had in C language, and the problem is that the logic gets buried inside all the error checking, and that makes code hard to read (and write).
Other languages use union types, enums, or other type system constructs to represent operations which may have multiple possible outcomes. You're correct that these languages don't support business logic exceptions, but that also isn't an acceptable practice outside of Java.
What is the acceptable practice outside of Java?
Do you prefer to check for errors after every function call? That's what you had in C language, and the problem is that the logic gets buried inside all the error checking, and that makes code hard to read (and write).
Pattern matching, nullable types, and the Try* method conventions are also ways of representing the potential for failure explicitly.
A b() throws C
fn b() -> Result<A, C>
A a = try {
b()
} catch (C c) {
new A()
}
val a = b() match {
Ok(a) => a
Failure(c) => A()
}What do you mean? if you add "throws Exception" to every method then you can defeat checked exceptions but please don't do this in production code
Config config;
try {
config = readConfig("/some/path/to/a/file/that/must/exist.txt")
} catch (IOException ex) {
throw new RuntimeException(ex);
}
doSomeStuffWith(config);
Realistically situations like this should be: Config config = readConfig!!!("/some/path.txt"); // !!! being some operator that says "shut up compiler crash if this happens"
doStuffWith(config);
People do not like checked exceptions because they either need to check and colour the whole stack or write way too much code to panic.Programmers are LAZY. Making them do verbose things when they can't possibly handle it are going to cause them to reject the language feature.
This is by design because each indicates catastrophic unrecoverable failure (you could have a couple of hand-wavy arguments about AVE dereferencing wrong address but, really, that indicates critical error in implementation or memory corruption still).
You should never see any of these regardless. Aside from abusing stackalloc by passing a large number I guess.