Summary:
James Gosling talks with Bill Venners about how to build solid apps, organize your catch clauses, scale checked exceptions, and deal with failure.
The ability to add new comments in this discussion is temporarily disabled.
Most recent reply: November 14, 2003 0:39 AM by
Mark
|
In this interview, James Gosling discusses exception handling and building "solid" software, and responds to concerns about checked exceptions, including scalability and empty catch clauses. Read this Artima.com interview: http://www.artima.com/intv/solid.htmlWhat do you think of James Gosling's comments?
|
|
|
It is true that mixing return values and "exception" values in the same return value is bad practice - but checked exceptions are not the only necessary solution. A more sane API could do the same job. For example, if you have a FileDescriptor object, "read()" should work normally while there are bytes remaining and should throw an EOF exception if there aren't. There should be another method "isEOF()" that enables the programmer to check for EOF.
For the "does a file exist" case, there should also be an "exists()" method (as there is in Java). However, it's a bit verbose if there's a checked exception as well, because you check the file exists with the method call AND then you must still handle the exception anyway.
Also, forcing someone to handle an exception, does not force them to handle an exception well - this is perhaps my biggest problem with checked exceptions. I have seen many cases where the exception is simply swallowed and nothing indicates there is a problem. Or alternatively, a new exception is throw but the initial exception is discarded so it's stack trace is unavailable (and this is sometimes in a part of code you may not have the source to change). I'd rather have an unchecked exception that makes everthing grind to a halt with a sensible stack trace rather than either of these cases.
As for having a centralised handling of Exceptions, this is one area where AOP holds promise. It makes sense to centralise the handling because it's often the same kind of problem you want to handle, eg RemoteExceptions. However, as James says, sometimes local information is useful too - but with AOP, this information would be available even if the handling were centralised. Unfortunately, Java will continue to force you to put handling code in locally even if handling them with AOP becomes widespread.
Finally, something in favour of checked exceptions: in TDD, conditions that raise exceptions should obviously be tested. Having checked exceptions is nice for this because they point this out.
Richard
|
|
|
I agree with what Richard says about handling exceptions well. All too often I see people using catch(Exception ex) or similar. Most of the time you just want to log the fact that an Exception occurred and let the user know there was a problem. There are occassions when you want to clean up resources (for example JDBC). But ofter you'll be catching (say in reflection) three or four exceptions - and each catch will just log the exception. I'd love to have a feature in Java where you could catch a list of Exceptions: catch ([InvocationTargetException, IllegalAccessException, NoSuchMethodException] ex) { ... log the above ... } because I just don't have anything more useful to do. And yet I don't want to use catch(Exception ex) because that would catch all manner of exceptions I *don't* want to handle, but instead let the propogate up the system.
IMHO the fact that there are so many checked exceptions have Java have led to a lot of bad code by beginners / intermediate level programmers in the company I'm currently working for. I don't think a language should be solely designed for someone with the common sense to come up with a good exception handling strategy for themselves - because the percentage of people who can do that off their own back is fairly small - but so that it minimises the problems those programmers can create :). There are pros and cons for both Java's checked approach and C#'s unchecked approach. I still haven't completely made up my own mind, but am currently leaning towards the latter. I'm trying out some Java projects using mainly / only unchecked exceptions (where I have influence over their type ... and wrapping checked ones in unchecked ones as necessary and where appropriate) ... so I'll be interested to see how that turns out.
Cheers, C
|
|
|
I agree with the other poster: Checked exceptions seems like a good idea, but there are some kinks in the way it is used in Java.
In addition to the fact that there really should be a java.lang.CheckedException superclass, there are a few exceptions that definately should not be checked. Having too many checked exceptions makes developers rebel against the checked exception system. Checked exceptions should never be used when the programmer has a chance to control the situation to where an exception could never occur. Making NumberFormatException a RuntimeException is an excellent example of this. (BTW: RuntimeException is not the clearest of names, either)
Top on my hit list are the following: * CloneNotSupportedException * UnsupportedEncodingException (most of the time that I have seen, the encoding string is hardcoded in the program) * MalformedURLException (URLs are quite often hardcoded) * HostNotFoundException for InetAddress.getLocalhost
Other than kinks in the usage, I like checked exceptions. I find it very strange that Java is the only language that really supports them.
|
|
|
The approach mentioned by Richard about whether an exception is predictable or not has worked well for me as a basic rule on whether to throw a checked exception or an unchecked exception.
Summing it up, I usually distinguish between predictable exceptions and unpredictable exceptions.
Am I able to predict an exception? Whenever I can avoid running into an exception when calling some 'tester' method first, then the method which might throw the exception throws an unchecked exception. Else the exception will become a checked exception.
Unpredictable exception = checked exception
For example I cannot predict network problems when connecting to some remote server: Somewhere between my computer and the remote system, somebody might have 'stumbled' over the network cable and thereby disconnects my two systems. I am not able to predict this kind of problem (if you have an algorithm for predicting such occurrences, please let me know). Therefore in my APIs for remote communication, one must catch connection related exceptions.
Predictable exception = unchecked exception
On the other hand, when calculating the circumference of a circle, I can test my arguments passed to the method in question beforehand. The radius must not be negative. A separate method 'boolean isRadiusValid(int aRadius)' helps me to predict any malfunction.
Or staying at the example reading data from a file:
Richard wrote "... A more sane API could do the same job. For example, if you have a FileDescriptor object, "read()" should work normally while there are bytes remaining and should throw an EOF exception if there aren't. There should be another method "isEOF()" that enables the programmer to check for EOF. ..."
As a side effect reading data is much more elegant using a while loop than waiting for the EOF exception (which should be unchecked): 'while (!isEOF)) { read()
}'
Surely this rule will not work for any API, but it looks like a good approach for lots of cases to me.
|
|
|
Regarding empty catch clauses.
There are two kinds of empty catch clauses: those that contain no executable statements and those that contain absolutely nothing.
Catch clauses that contain absolutely nothing give me a creepy feeling. However, when the right thing is to respond to an exception by doing absolutely nothing, I find the compiler's insistence on a catch clause an excellent reminder to provide useful comments.
The trick with empty catch clauses is to make sure that they contain comments that explain _why_ you feel it's appropriate to do nothing.
A good example I ran into recently was handling an exception from an SMTP client connection while trying to close it, after successfully sending the SMTP QUIT command. At this point, you really don't care whether the close operation on the socket is successful. But it's worth documenting why, and the empty catch block is an excellent place to do so.
|
|
|
I spent 9 years in the 5ESS project at AT&T and then Lucent. I developed the procedures that installed new software onto a running switch using the duplex hardware. I gained a lot of knowledge about the internals of that system because I got to be there when software failed due to scalibilty, requirements failures and the whole gammit of things that can happen when software is deployed into an environment of basically random requirements. These environments consist of huge amounts of 'configuration based' functionality. Customers can configure their switches to the limits of the configuration tools abilities. And, sometimes they learn about internal tools and use those to select operating characteristics that are not validated, or tested.
When we would boot the new software release up on the offline hardware, many times things would seem great. We'd switch over to that software and all heck would break loose.
One of the things I learned from this is that 'everything matters' if you want your system to be 'solid' (as James calls it). There are error handling strategies in the switch that involve escalating levels of initialization, process isolation, hardware isolation etc. All of these strategies are based on exception processing. Things are happening that you do not expect. You have to deal with it.
In this type of application, a C language program would have at most 5 instructions, and typically 3 between branches because of the number of error conditions that need to be dealt with and the data that needs to be validated, repeatedly. In perfect systems, you can test things once. When you finally recognize that you can have a memory error that is not detectable by the hardware, you learn to check things more often.
Microsoft and the MSDOS crowd argued for years on usenet through the early 1980s and into the 1990s, that DOS was fine. Noone needed no darn memory protection. Those silly programmers needed to write good code. Then, there wouldn't be all these nasty data loosing, disk eating crashes. Well guess what. Microsoft finally learned that the UNIX crowd knew what they were talking about. Memory and process protection is important.
Hopefully the next thing that they'll learn is that exception processing is vital!
James is one of these people who has recognized that the world is not perfect. Unfortunately, there are a lot of untrained programmers who have not learned this.
|
|
|
Checked Exceptions would appear to provide two benefits: Code based Documentation, & enforced handling.
The Code based Documentation is not limited to Checked Exceptions, you can put RuntimeExceptions in the throws clause (we do this for two reasons, 1st simply for the documentation, and 2nd because IntelliJ will automatically generate the try-catch structure).
As for the enforced handling, this only makes sense if there is a likelihood of being able to handle it. For highly specific exceptions, there is a "chance" of locally handling the problem. But it would be a poor separation of concerns (and presumptive of a highly interactive UI) for the local level to even know how to ask the user to resolve the problem (e.g. you might have two totally different UIs: Rich Swing Client, and Web Server).
The related problem is with Exception conversion AND Exception hierarchies, once you loose the specific nature of the problem, it is impossible to use just the Exception type and have meaningful automated (non User interaction) corrective behavior. Yet with Checked Exceptions, like IOException, while I might be able to deal with a FileNotFoundException or a MalformedURLException, the fact that the methods usually (due to design to interface) throw IOException means that I am forced to catch IOException when there is nothing that I can really do with it.
|
|
|
Checked Exceptions would appear to provide two benefits: Code based Documentation, & enforced handling.
The Code based Documentation is not limited to Checked Exceptions, you can put RuntimeExceptions in the throws clause (we do this for two reasons, 1st simply for the documentation, and 2nd because IntelliJ will automatically generate the try-catch structure).
As for the enforced handling, this only makes sense if there is a likelihood of being able to handle it. For highly specific exceptions, there is a "chance" of locally handling the problem. But it would be a poor separation of concerns (and presumptive of a highly interactive UI) for the local level to even know how to ask the user to resolve the problem (e.g. you might have two totally different UIs: Rich Swing Client, and Web Server).
The related problem is with Exception conversion AND Exception hierarchies, once you loose the specific nature of the problem, it is impossible to use just the Exception type and have meaningful automated (non User interaction) corrective behavior. Yet with Checked Exceptions, like IOException, while I might be able to deal with a FileNotFoundException or a MalformedURLException, the fact that the methods usually (due to design to interface) throw IOException means that I am forced to catch IOException when there is nothing that I can really do with it.
|
|
|
I programmed in C/C++ for about 10 years prior to using Java, and not having a reminder to catch an exception is a significant problem. I aplaud James for attempting to correct this problem. It is fairly obvious that interface issues can be solved by nesting exceptions or even converting them to runtime exceptions. I believe the only major issue with checked exceptions that cannot be overcome fairly easily is the empty catch clause. I actually feel that James would be doing himself more justice to acknowledge this as a real world problem as opposed to seemingly implying that the education system is at fault. Often times contractors are hired by companies to do short term work under tremendous time pressures. This is going to happen whether or not our education system encourages it, due to haste and or laziness. I know that for myself the empty catch clause is not a problem because I and my close colleagues do not use empty catch clauses without a great deal of scrutinization of the situation at hand. Checked exceptions have VASTLY improved error checking in my programs.
I have a partial workaround and a proposed solution for those who are having problems. Unfortunately the solution would not be backwards compatible.
The workaround is to search inherited source for empty catch clauses and DO SOMETHING ABOUT THEM! You have bugs in your source if error checking isn't being handled! Of course this would not work if you purchased a third party library for which you do not have the source.
The proposed solution would be to have the empty catch clause convert all exceptions it catches into runtime exceptions and rethrow them i.e. the exact behavior you would have had without the checked exceptions in place. Yes this allows sloppy or lazy programmers to circumvent the checked exceptions mechanism but they are doing this already with far worse consequences. In addition, it allows meticulous programmers to still use checked exceptions and it eliminates the possibility of the horrendous down stream bugs that can be caused by an empty catch clause, the same bugs which should be acknowledged by the creators of the checked exception mechanism as highly problematic. If one truly wanted and needed an explicit empty catch clause that did nothing, a keyword could be added to the language such as explicitempty.
catch( IOException ex ){ explicitempty; }
The truly lazy programmer will not go to the effort of typing in "explicitempty;" especially if the recommended behavior is to leave the clause empty.
In short, for those who are already not using the checked exceptions mechanism, the behavior would default back to that of not having checked exceptions. However, the programmer would have to consiously acknowledge the exception even if they only choose to convert it into a runtime exception.
|
|