An Uneducated View of Checked vs. Unchecked Exceptions
marcorogers.com
marcorogers.com
The truth is that the 'handling' is often much less robust than it ought to be, which in turn will lead to the hiding of problems at lower layers.
A simple example would be an I/O error when reading a file, if you 'handle the exception' then you have done absolutely nothing to take care of the underlying problem.
Personally I'm a huge fan of spending the extra time required when writing lower level code to make sure that all cases are handled properly, instead of throwing an exception at the slightest sign of trouble.
A pet example of this is the UTF-8 and unicode conversion library, when presented with an 'undefined' character lots of libraries will cause a whole series of exceptions to be triggered because of a single character issue in some data to be imported.
If such a library were defined 'properly' then you'd tell it what to do with 'bad' input, toss it, replace it with ##undefined character## (or any other string you come up with) or, in case you want it, throw an exception. The defaults should be sensible and should maximize productivity.
That way the user of such a library knows the library will handle the job with a minimum of fuss.
I see exceptions mostly being used as a way to be absolved from further responsibility from an issue, and that's really not the way they should be used.
Run time errors are uniformly bad, exceptions really are a form of runtime error and should be avoided as much as possible. Especially in lower level code.
As to your string decoder example, the sensible default should be to throw an exception, with an easily available out to replacements etc. as you suggest. String conversion across multiple code pages is an interesting example precisely because data loss is so easy, and if the problems aren't raised early in the data flow, it can be hard to figure out where it went wrong. The "default" manifests itself in Windows programs as '?' - rather annoying to debug when the first time you see it is on the UI, while other concrete are plentiful on the web. A very common example is balanced "smart" quotes, which frequently end up encoded in either the web page or the RSS feed in a different encoding than that described in the HTTP header, or in the meta tag, or according to the relevant standards, etc.
Yes, runtime errors should be avoided, but not by the lower-level code; rather, the lower-level code should throw exceptions when error conditions occur, but have ways and means for programs that care about the exceptional conditions to not throw exceptions. This isn't always possible, though. Consider TFA's example of file not found:
> The caller can't recover from a file not found error
The caller can test to see if a file exists before trying to read it, but non-transactional interactions like this with the file system are subject to races; the file might exist one microsecond and be gone the next. But it is also obviously true that the caller can recover from a file not found error. Concrete example: consider a thumbnail generating service, which checks to see if a cached thumbnail file exists before doing the expensive work of creating it if necessary. Here, a file not found exception (perhaps caused by concurrent cleanup of cache, for random operational reasons) is indeed not fatal.
That's exactly where the controversy lies.
The string conversion issue was brought up because I think it is an excellent case of 'programmer knows best', but the exception causes the whole dataset to be rejected, not just the one record out of 3.9 million that were fine. That's obviously sub-optimal. There has to be a better way to deal with that sort of thing, such as for instance an 'output' vector where data that couldn't be processed can be sent, or a series of warnings / errors attached to certain offsetts in your data. Then after running the conversion you can decide if you want to drop all of it (which is what happens now) and fix the input data, or discard those records (sometimes that is an option), or manually edit those and then run the import on those few records again.
Because exceptions are a mechanism you can then 'use' them for whatever you feel like. Just like you can use setjmp/longjmp in C for all kinds of creative stuff you can use exceptions to change the dynamic of your program. And this leads to some very difficult to trace code because exceptions break the normal flow.
Almost all exceptions in for instance the python mysql layer need to be handled by your application otherwise you will be exposing the user of your application (or website) to a runtime error and that is not good, not handling those is not really an option (unless you don't like your users much :) ).
So, in production grade software you'll find that almost all possible exceptions are indeed handled, and if possible as close as possible to the location of the error to make sure the fall-out of the problem is minimized.
Generally speaking, the higher up the handler the less information there is about the context of the exception, the smaller the chance you can handle it in a meaningful way.
This style of coding makes it not rare to see half the code to be about exception handling, the other half actual 'productive' code. Especially Java suffers from this very much.
Taking your database interaction example, here are the kinds of situations I could see, and responses:
* Failure to connect; this is pretty fatal, unless there's home-spun failover support, it should not be handled at a low level
* Query syntax / API error: this is a programming error, should be logged and the current process (request, whatever) aborted
* Constraint failure: choosing whether to handle constraints on the database side, or on the app server side, or both sides, is an architectural decision with tradeoffs. Ideally constraints should be portable, so that they can be transported all the way to the UI, and checked at every stage along the way. These constraint failures might be signalled through exceptions, but that's a tactical choice, and may not be the best one. Generally, exceptions only signal for the first error, but usually there is more than one constraint, and multiple actionable failed constraints should be propagated to avoid wasting the end user's time (consider the simplistic compiler which halts on first error).
My point is that exceptions should generally either be things you can't reasonably handle, with a non-throwing "out" for situations where you do expect to be able to handle the problem. If you expect to be able to work around the issue, i.e. find a solution locally, low down in the stack, then you probably shouldn't be catching an exception in order to do so, unless that's impossible (like the race condition I mentioned earlier).
That we completely agree on.
As for that race condition, I think that a situation like that could easily be avoided by having a single task be responsible for maintaining that data structure and to send it messages to update the state atomically.
That way you avoid the race completely.
Race conditions usually are a sign of multiple parties having access to a single resource where you'd be better served having one 'handler' process in charge of the data object that multiple parties want to access. It can then be the arbiter of who has access.
If the changes are a-synchronous then you could use a 'fire and forget' style queuing mechanism where the only thing that matters is that you know the message was received, the more tightly coupled example you gave could be handled reasonably elegant with a client-server model where the server will handle only client at a time for a particular resource.
Data objects not being 'alive' enough is the cause of many race conditions, by assigning a handler you achieve two things in one go, the first is an abstraction of interaction with the data object to a series of messages to a handler, the second the removal of race conditions and their 'normal' solutions (such as locks).
The programmer first has to know that he has to make decision what to do with partially wrong input and there is no better way to let him know than throwing exception in his face. Then he will know that there is configuration option that will let him to go around the problem one or other way.
Default option in case of any trouble should be exception bubbling up with information what can be done to avoid it.
This pattern seems to recur a lot in programming debates, where people don't seem to realize that it doesn't matter how wonderful your theory is if when you try it out, it's wrong. That's where checked exceptions are.
The time for theorizing about their effects on programs is long, long past. Who cares whether they theoretically force programmers to write better code? We now know that in reality, it actually results in worse programs the majority of the time. When theory and reality disagree, theory is wrong. Why anybody still talks about their theoretical design goals is beyond me.
As near as I can tell, the debate is over if you ignore the people who haven't let their theories catch up to reality. If someone's got a success story posted about how checked exceptions saved their program's bum, I'm interested in the link, but I haven't seen one.
(I say it that way because when you have something enough people will read you can always find someone to pop up and claim that "X is good", but I'm specifically asking for arguments that already existed prior to this post, to avoid selection bias effects.)
But on the other hand, I must point out: checked exceptions certainly force you to address the issue of the exception being thrown, and as a result, the failure mode that they prevent can't occur, so the evidence you're seeking is more or less impossible.
On the other hand, checked exceptions have their own failure modes, down to human behaviour: rather than having to rewrite every method declaration on the call graph to throw the appropriate list of exceptions, some bad apples just catch exceptions and do nothing with them. This shuts up the compiler, but harms product quality.
IMO, the theory behind checked exceptions is actually fundamentally mistaken. Exceptions aren't much good if you're constantly trying to catch them. Exceptions work best if you write your code in a transactional style, more or less assuming that an exception can occur at any moment, on any operation. This can be tricky: it can force you to make or keep copies of structures when doing large structural modifications; but this difficulty is not strictly due to exceptions, as failures modes need to be dealt with in any case.
The problem is it never works. There are two common strategies that I've encountered to deal with 90+% of checked exceptions:
-Most code just bubbles the exceptions up the top layer (i.e., the UI), which clutters up every method signature along the way. The UI then has a big handler that catches all exceptions, and just logs an error and shuts down the program. Even on the off chance that the top-level could do something more productive, the problem is that the stack has been unwound too far and it's too late to fix whatever the problem is.
-The other common approach is to wrap every checked exception inside a custom unchecked exception that is just thrown. This is my preferred solution (at least it doesn't clutter up every method signature), but I have to explicitly convert every checked exception into an unchecked one - which is just a bit of a hassle.
I give Sun some props for trying out on a limb with a really cool sounding idea. But I think most people have realized it doesn't work very well in practice ...
I think checked exceptions are a bad idea for a couple of reasons. First is their most acclaimed benefit: they force you to deal with the exception immediately, even if you have no solution for it. You have to decide whether to catch the exception or add a throws clause, which changes the method signature and breaks it for any users of your method. The the default decision for many programmers is to log the exception and swallow it, intending to come back later. I have had to dig through applications to find sql exceptions that were caught but not dealt with.
The second big problem I find is the poor design of exception classes. It is not uncommon to find a class that throws a half-dozen exceptions. Every time you call a new method you have a new exception to deal with. Either it should have a single exception with different error codes for the different cases, or the exceptions should be subclassed from a master exception.
The use of the word "immediately" informs your misunderstanding of the purpose of a checked exception.
Checked exceptions are enforced at compile time (thus "checked" exception). There is no such thing as 'temporal immediacy' at compile time (unlike runtime exceptions). There is potential for spatial immediacy (if we consider the static call stack), but that is not what the compiler cares about. (In other words, you can catch exception Foo at the call site and delegate handling up the call stack by throwing a Bar (cause: Foo)).
A compiler enforced handling of an exception declaration insures that the program handles the exception at some level in the call stack, as apparent to the compiler.
This whole (endless) discussion regarding checked/unchecked very much mirrors the static-typing/dynamic-typing discussion. You can either consider the merits pragmatically, or, you can adopt a dogmatic (religious) point of view that embraces one paradigm and dismisses the other.
I would think the fact of the possibility of mapping exceptions (according to some abstraction order mirroring the layering of the overall system) calls for "acknowledge" instead of "solve".
And compilers can help with robustness of programs, but their influence on the robustness of programmers is not immediately evident. So perhaps "suggests" instead of "encourage" for a subset of programmers?
eeeverbody() throws Exception;
There, I dealt with exception handling.
If used judiciously, I think checked exceptions do have benefits. There are classes of errors that are exceptional but also conceivably recoverable. The fact that they are exceptional means it is important to draw attention to them (they may otherwise be overlooked). The fact that they are recoverable justifies requiring calling code to make a choice - handle or propagate the error. Even in these cases, the code can get messy, but I see that as a fact of life: writing robust code isn't easy.
http://www.rockstarprogrammer.org/post/2007/jun/09/java-exce...