The case against checked exceptions

2018-12-31 23:23发布

For a number of years now I have been unable to get a decent answer to the following question: why are some developers so against checked exceptions? I have had numerous conversations, read things on blogs, read what Bruce Eckel had to say (the first person I saw speak out against them).

I am currently writing some new code and paying very careful attention to how I deal with exceptions. I am trying to see the point of view of the "we don't like checked exceptions" crowd and I still cannot see it.

Every conversation I have ends with the same question going unanswered... let me set it up:

In general (from how Java was designed),

  • Error is for things that should never be caught (VM has a peanut allergy and someone dropped a jar of peanuts on it)
  • RuntimeException is for things that the programmer did wrong (programmer walked off the end of an array)
  • Exception (except RuntimeException) is for things that are out of the programmer's control (disk fills up while writing to the file system, file handle limit for the process has been reached and you cannot open any more files)
  • Throwable is simply the parent of all of the exception types.

A common argument I hear is that if an exception happens then all the developer is going to do is exit the program.

Another common argument I hear is that checked exceptions make it harder to refactor code.

For the "all I am going to do is exit" argument I say that even if you are exiting you need to display a reasonable error message. If you are just punting on handling errors then your users won't be overly happy when the program exits without a clear indication of why.

For the "it makes it hard to refactor" crowd, that indicates that the proper level of abstraction wasn't chosen. Rather than declare a method throws an IOException, the IOException should be transformed into an exception that is more suited for what is going on.

I don't have an issue with wrapping Main with catch(Exception) (or in some cases catch(Throwable) to ensure that the program can exit gracefully - but I always catch the specific exceptions I need to. Doing that allows me to, at the very least, display an appropriate error message.

The question that people never reply to is this:

If you throw RuntimeException subclasses instead of Exception subclasses then how do you know what you are supposed to catch?

If the answer is catch Exception then you are also dealing with programmer errors the same way as system exceptions. That seems wrong to me.

If you catch Throwable then you are treating system exceptions and VM errors (and the like) the same way. That seems wrong to me.

If the answer is that you catch only the exceptions you know are thrown then how do you know what ones are thrown? What happens when programmer X throws a new exception and forgot to catch it? That seems very dangerous to me.

I would say that a program that displays a stack trace is wrong. Do people who don't like checked exceptions not feel that way?

So, if you don't like checked exceptions can you explain why not AND answer the question that doesn't get answered please?

Edit: I am not looking for advice on when to use either model, what I am looking for is why people extend from RuntimeException because they don't like extending from Exception and/or why they catch an exception and then rethrow a RuntimeException rather than add throws to their method. I want to understand the motivation for disliking checked exceptions.

30条回答
浮光初槿花落
2楼-- · 2018-12-31 23:56

The good proves that Checked Exception are not needed are:

  1. A lot of framework that does some work for Java. Like Spring that wraps JDBC exception to unchecked exceptions, throwing messages to the log
  2. Lot of languages that came after java, even on top on java platform - they do not use them
  3. Checked exceptions, it is kind prediction about how the client would use the code that throws an exception. But a developer who writes this code would never know about the system and business that client of code is working in. As an example Interfcace methods that force to throw checked exception. There are 100 implementation over the system, 50 or even 90 of implementations do not throw this exception, but the client still must to catch this exception if he user reference to that interface. Those 50 or 90 implementations tend to handle those exceptions inside themself, putting exception to the log (and this is good behavior for them). What we should do with that? I would better have some background logic that would do all that job - sending message to the log. And If I, as a client of code, would feel I need handle the exception - I will do it. I may forget about it, right - but if I use TDD, all my steps are covered and I know what I want.
  4. Another example when I'm working with I/O in java, it forces me to check all exception, if file does not exists? what I should do with that? If it does not exists, the system would not go to the next step. The client of this method, would not get expected content from that file - he can handle Runtime Exception, otherwise I should first check Checked Exception, put a message to log, then throw exception up out form the method. No...no - I would better do it automatically with RuntimeEception, that does it / lits up automatically. There is no any sense to handle it manually - I would be happy I saw an error message in the log (AOP can help with that.. something that fixes java). If, eventually, I deice that system should shows pop-up message to the end user - I will show it, not a problem.

I was happy if java would provide me with a choice what to use, when working with core libs, like I/O. Like provides two copies of same classes - one wrapped with RuntimeEception. Then we can compare what people would use. For now, though, many people would better go for some framework on top on java, or different language. Like Scala, JRuby whatever. Many just believe that SUN was right.

查看更多
倾城一夜雪
3楼-- · 2018-12-31 23:57

SNR

Firstly, checked exceptions decrease the "signal-to-noise ratio" for the code. Anders Hejlsberg also talks about imperative vs declarative programming which is a similar concept. Anyway consider the following code snippets:

Update UI from non UI-thread in Java:

try {  
    // Run the update code on the Swing thread  
    SwingUtilities.invokeAndWait(() -> {  
        try {
            // Update UI value from the file system data  
            FileUtility f = new FileUtility();  
            uiComponent.setValue(f.readSomething());
        } catch (IOException e) {  
            throw new UncheckedIOException(e);
        }
    });
} catch (InterruptedException ex) {  
    throw new IllegalStateException("Interrupted updating UI", ex);  
} catch (InvocationTargetException ex) {
    throw new IllegalStateException("Invocation target exception updating UI", ex);
}

Update UI from non UI-thread in C#:

private void UpdateValue()  
{  
   // Ensure the update happens on the UI thread  
   if (InvokeRequired)  
   {  
       Invoke(new MethodInvoker(UpdateValue));  
   }  
   else  
   {  
       // Update UI value from the file system data  
       FileUtility f = new FileUtility();  
       uiComponent.Value = f.ReadSomething();  
   }  
}  

Which seems a lot clearer to me. When you start to do more and more UI work in Swing checked exceptions start to become really annoying and useless.

Jail Break

To implement even the most basic of implementations, such as Java's List interface, checked exceptions as a tool for design by contract fall down. Consider a list that is backed by a database or a filesystem or any other implementation that throws a checked exception. The only possible implementation is to catch the checked exception and rethrow it as an unchecked exception:

@Override
public void clear()  
{  
   try  
   {  
       backingImplementation.clear();  
   }  
   catch (CheckedBackingImplException ex)  
   {  
       throw new IllegalStateException("Error clearing underlying list.", ex);  
   }  
}  

And now you have to ask what is the point of all that code? The checked exceptions just add noise, the exception has been caught but not handled and design by contract (in terms of checked exceptions) has broken down.

Conclusion

  • Catching exceptions is different to handling them.
  • Checked exceptions add noise to the code.
  • Exception handling works well in C# without them.

I blogged about this previously.

查看更多
看风景的人
4楼-- · 2018-12-31 23:57

The article Effective Java Exceptions explains nicely when to use unchecked and when to use checked exceptions. Here are some quotes from that article to highlight the main points:

Contingency: An expected condition demanding an alternative response from a method that can be expressed in terms of the method's intended purpose. The caller of the method expects these kinds of conditions and has a strategy for coping with them.

Fault: An unplanned condition that prevents a method from achieving its intended purpose that cannot be described without reference to the method's internal implementation.

(SO doesn't allow tables, so you might want to read the following from the original page...)

Contingency

  • Is considered to be: A part of the design
  • Is expected to happen: Regularly but rarely
  • Who cares about it: The upstream code that invokes the method
  • Examples: Alternative return modes
  • Best Mapping: A checked exception

Fault

  • Is considered to be: A nasty surprise
  • Is expected to happen: Never
  • Who cares about it: The people who need to fix the problem
  • Examples: Programming bugs, hardware malfunctions, configuration mistakes, missing files, unavailable servers
  • Best Mapping: An unchecked exception
查看更多
皆成旧梦
5楼-- · 2018-12-31 23:59

The thing about checked exceptions is that they are not really exceptions by the usual understanding of the concept. Instead, they are API alternative return values.

The whole idea of exceptions is that an error thrown somewhere way down the call chain can bubble up and be handled by code somewhere further up, without the intervening code having to worry about it. Checked exceptions, on the other hand, require every level of code between the thrower and the catcher to declare they know about all forms of exception that can go through them. This is really little different in practice to if checked exceptions were simply special return values which the caller had to check for. eg.[pseudocode]:

public [int or IOException] writeToStream(OutputStream stream) {
    [void or IOException] a= stream.write(mybytes);
    if (a instanceof IOException)
        return a;
    return mybytes.length;
}

Since Java can't do alternative return values, or simple inline tuples as return values, checked exceptions are are a reasonable response.

The problem is that a lot of code, including great swathes of the standard library, misuse checked exceptions for real exceptional conditions that you might very well want to catch several levels up. Why is IOException not a RuntimeException? In every other language I can let an IO exception happen, and if I do nothing to handle it, my application will stop and I'll get a handy stack trace to look at. This is the best thing that can happen.

Maybe two methods up from the example you want to catch all IOExceptions from the whole writing-to-stream process, abort the process and jump into the error reporting code; in Java you can't do that without adding ‘throws IOException’ at every call level, even levels that themselves do no IO. Such methods should not need to know about the exception handling; having to add exceptions to their signatures:

  1. unnecessarily increases coupling;
  2. makes interface signatures very brittle to change;
  3. makes the code less readable;
  4. is so annoying that it the common programmer reaction is to defeat the system by doing something horrible like ‘throws Exception’, ‘catch (Exception e) {}’, or wrapping everything in a RuntimeException (which makes debugging harder).

And then there's plenty of just ridiculous library exceptions like:

try {
    httpconn.setRequestMethod("POST");
} catch (ProtocolException e) {
    throw new CanNeverHappenException("oh dear!");
}

When you have to clutter up your code with ludicrous crud like this, it is no wonder checked exceptions receive a bunch of hate, even though really this is just simple poor API design.

Another particular bad effect is on Inversion of Control, where component A supplies a callback to generic component B. Component A wants to be able to let an exception throw from its callback back to the place where it called component B, but it can't because that would change the callback interface which is fixed by B. A can only do it by wrapping the real exception in a RuntimeException, which is yet more exception-handling boilerplate to write.

Checked exceptions as implemented in Java and its standard library mean boilerplate, boilerplate, boilerplate. In an already verbose language this is not a win.

查看更多
皆成旧梦
6楼-- · 2019-01-01 00:00

The programmer needs to know all of the exceptions that a method may throw, in order to use it correctly. So, beating him over the head with just some of the exceptions does not necessarily help a careless programmer avoid errors.

The slim benefit is outweighed by the burdensome cost (especially in larger, less flexible code bases where constantly modifying the interface signatures is not practical).

Static analysis can be nice, but truly reliable static analysis often inflexibly demands strict work from the programmer. There is a cost-benefit calculation, and the bar needs to be set high for a check that leads to a compile time error. It would be more helpful if the IDE took on the role of communicating which exceptions a method may throw (including which are unavoidable). Although perhaps it would not be as reliable without forced exception declarations, most exceptions would still be declared in documentation, and the reliability of an IDE warning is not so crucial.

查看更多
余生请多指教
7楼-- · 2019-01-01 00:01

To attempt to address just the unanswered question:

If you throw RuntimeException subclasses instead of Exception subclasses then how do you know what you are supposed to catch?

The question contains specious reasoning IMHO. Just because the API tells you what it throws doesn't mean you deal with it in the same way in all cases. To put it another way, the exceptions you need to catch vary depending on the context in which you use the component throwing the exception.

For example:

If I'm writing a connection tester for a database, or something to check the validity of a user entered XPath, then I'd probably want to catch and report on all checked and unchecked exceptions that are thrown by the operation.

If, however, I am writing a processing engine, I will likely treat an XPathException (checked) in the same way as an NPE: I would let it run up to the top of the worker thread, skip the rest of that batch, log the issue (or send it to a support department for diagnosis) and leave feedback for the user to contact support.

查看更多
登录 后发表回答