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.
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.
But importance of the failure is determined completely by the program, not the library.
Grep fails to open a file for reading -> message the user and exit
Nuclear reactor controller fails to read important a file -> initiate reactor shutdown or something.
If file read is critical, you have to handle failure no matter what the interface is. Because you know that disk can fail.
E.g. in the case of grep. Say I wrote grep and want it to be generic. I wrote a library that implements grep. Then I write a grep GUI tool. OOps. It exits if the file isn't found instead of showing an error dialog. With exceptions this is communicated up the layers. That's their purpose.
If I'm writing generic code and that code is used in a nuclear reactor I would very much not like my failure code to decide what to do. That's why we have exceptions, they punt the responsibility to the next person up the chain. I have no idea how to initiate a nuclear reactor shutdown etc.
But as API authors how can we make sure the person who writes the code up the chain knows that this is something crucial?
They all use library for file reading.
As author of file reading library you don’t know how critical is the failure to open a file or how it should be handled. My point was that You know those things only at application level.
So it feels a bit misguided to decide at library level which failures are important. (I.e. which are checked vs which are unchecked)
> But as API authors how can we make sure
First thought: documentation. (which is required for both, checked and unchecked exceptions).
But overall, it’s not library author’s responsibility or ability to “ensure”. You can’t force correct handling of exception. At best one can make it a bit more annoying to ignore, and convenient to do the right thing.
My view:
- all exceptions should be unchecked
- assume all code throws
- if “log and exit/continue” is not enough and you need to know exact exception types, dig into the docs.
- read docs during lib version upgrades
As a library author if you don't feel you need it then don't use it. It's optional. That's the beauty of it.
> First thought: documentation. (which is required for both, checked and unchecked exceptions).
Documentation < compiler checks.
> But overall, it’s not library author’s responsibility or ability to “ensure”.
Here we differ. Part of that is the domain we spend most of our time in. If you're giving me the example of grep then great, I see your point. Notice I'm giving an example of a high availability huge enterprise app.
Assuming all code throws forces overly defensive code which can cause a lot of problems. Yes, there's always a "catch all" but that's not a valid case for these sorts of apps.
Exit or continue are never enough for these sorts of applications.
We have dozens of dependencies and sometimes more. Some of them have mountains of documentations. Often we delay updates since these are enterprises. Going over 3 years of diffs in docs is just not a feasible option. Just the diffs in Spring Boot alone and all its dependencies would take several years.
The library author can’t know what’s crucial in the context of my program.
However I've written several pieces of code where allocating a large buffer could fail but where this failure was not crucial. So I handled the out-of-memory exception and just moved on.
If you're unsure then make it a runtime exception. I agree that a lot of the problems people have with checked exceptions is that people over use them.
You don't want to handle the checked exception wrap it with a runtime exception. But as an author if there's something important, I want to give the user of the library as much help as I can.
You're still ignoring other people's point in this thread: you (the author of a library/package) cannot decide what is important for the users of your library. You can guess, but your guess will always be wrong for a subset of your downstream users.
And speaking from the other side, as an application developer: for most of my code, an ArrayIndexOutOfBoundsException is a pretty serious exception. How do I go about making it a checked exception?
You're ignoring the fact that I'm not saying EVERYTHING should be a checked exception. Quite the opposite.
I can choose to handle the error myself or let the shell script crash by having “set -e”.
Grep returns a non zero error code and it’s still up to me to decide how to handle it.
> I can choose to handle the error myself
When we write generic library code we have no way of knowing how to handle an error in the full system. We want to concentrate some of the error handling while still making localized decisions in various places.
E.g. I want metrics and observability details updated, yet I want locally to retry some behavior. Doing this every time there's an error would force every library and every part that can fail in my code to know how I plan to handle the error.
This can be solved with good documentation, but it can also be solved in the type system. Typically, if I can get my computer to do work for me (e.g. make sure that I've handled all possible error cases) then I'm much more confident in my code, hence why I see a lot of value in a well-designed checked exceptions system.
Exactly. I think this is the real crux about what's wrong with checked exceptions. It puts the responsibility to decide what exceptions are important on the library, where it doesn't belong. Only the user of the library knows that.
I'm not a friend of checked exceptions myself, but I still think it's the opposite.
¹ which leads to the real issue with checked exceptions: they propagate through dependencies, if one nested dependency adds another checked exception, all dependencies have to add the exception or handle it themselves.
It would be another matter if they were designed such that you could fix an issue and continue the call on the happy branch, but I suspect the cases where something like that would be applicable are very few.
That should be: message the user and process the next file
I get a failure, what do I do?
I can throw an exception, but this is important how do I know the user of my API will actually do the cleanup after me. I can't do the cleanup since this is a part of an API not the actual usage of the API. How can I give the user of this API the right set of hint that "you need to pay attention to this" without worrying too much.
Checked exceptions are that. A large part of the problem is when people use them for things that aren't important, but when they are important I can just declare a "throws" and know that someone will handle the exception along the chain.
With runtime exceptions I have no guarantee.
My naive take on this would be “just use raii/closable”. (But easily and likely, I misunderstood)
But let's go further. Let's say I have an API x(). This API accesses my database as part of a larger transaction.
I invoke x() and it fails with an SQLException which is declared. I can revert the transaction or I can choose to retry x(). The checked exception notifies me that there's an important decision I need to make at this point, cleanup is one option but in some cases there are more. Furthermore, cleanup isn't always enough. In a case of an IOException I'd often want to notify the user e.g. if the disk is out of space I want to show an error message somewhere...
That will be part of lib documentation, which should be read & understood regardless of existence of checked exception.
> That will be part of lib documentation, which should be read & understood regardless of existence of checked exception.
First, checked exceptions *are* documentation for the lib. The best kind of documentation.
Second, really?
I would love to live in your world where people read documentation and where we all perfectly update the docs for everything. But both sides of this equation leave a lot to be desired in my world.
I don't read the documentation of most changes to most libraries. If I did that I would never get anything done. I can't even keep up with every commit that goes into the project I'm running. There are too many changes and too much code (I'm talking 30+ non-trivial merges per day).
Any decent C# linter will notice that the class exposes (IDisposable) and warn you that you should at least be wrapping it in a “using” block.
Also, notice that a linter and a compiler error are different. I agree that people *should* always use a good linter and IDE. The reality is sadly far from that.